import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1198588 - Remove unused MSG-specific code from AudioStream. r=padenot (79bd29d60c) - Bug 1187195 - Remove all agents with null window. r=baku (c3733647bb) - Bug 1137151: Marked destructors of ref-counted audio-manager classes as protected, r=dhylands (d0b84530f6) - Bug 1179181 - Store separate volume setting into setting database. r=baku (a6cb4bab03) - Bug 1175447 - mono audio support. r=padenot, r=sotaro (ce011f6556) - Bug 1140763 - Build dom/media/gtest in unified mode; r=cpearce (b3061b094b) - Bug 1194442 - Code clean up of AudioManager r=alwu (d25f51cda2) - Bug 1137659 - Re-enable LogShake xpcshell tests on B2G Emulator debug. r=me (f8b037756a) - Bug 1136777 - Enable LogShake by default and listen for new content events. r=fabrice, r=gerard-majax (1cf207dbe8) - Bug 1101994 - Add a low-pass filter to LogShake's shake detection and refactor unit tests to be less redundant. r=mhenretty, r=gerard-majax (7f59e328ec) - Bug 1144499 - Consolidate two long-running tests in test_logshake_gonk into one test of equivalent strength. r=gerard-majax (765d0bc243) - Bug 1079763 - Compress logs produced by LogShake. r=gerard-majax (26e5ca1dec) - Bug 1181561 - Expose a Kill Switch enabling/disabling. r=dhylands, sr=sicking (03d2754eea) - Bug 1174682 - Rename logshake-screenshot to screenshot.png. r=gerard-majax (b6faaa3fc2) - Bug 1188999 - LogShake does not fetch about:memory reports. r=gerard-majax (03a75e6408) - let-var (1114e25a65) - Bug 1188487 - Add API to BrowserElement to mute and set volume. r=fabrice (3d49951639) - Bug 1195801 - Add GetStructuredData() method to Browser API. r=kanru, r=bholley (b4cc0cba61) - Bug 1167465 - Exposing Allowed Audio Channels in System App's Window, r=alwu, r=fabrice (e8d59eb0e1) - fix build (8a58b834fe) - Bug 1183301 - GetAllowedAudioChannels should not throw an exception if nsIFrameElement is not ready. r=baku (1adbb9b140) - Bug 1184821 - Use CheckAllPermissions in BrowserElement.webidl r=bz (65a22b5bc1) - Bug 1206212 - Remove AUDIO_STREAM_FM after KK r=alwu (4d83c541dc) - Bug 1131927 - [Automounter] Add reporter to get current information. r=dhylands (d5d2e94b8c) - Bug 1197689 - Avoid unnecessary sync IPC in AudioChannelManager ctor. r=baku (128abefeb6) - Bug 1208155 - Make hal functions explicit in AudioManager r=alwu (d320b83fa7) - Bug 1179691 - [Automounter] Fix the problem of get format and share request in the same time. r=dhylands (768ad35244) - Bug 1137151: Marked destructors of ref-counted auto-mounter classes as protected, r=dhylands (c8cf90bdff) - Bug 1158047 - [AutoMounter] Resolve the problem of stucking in UMS_CONFIGURING state. r=dhylands (793f023fd2) - Bug 1012403 - Reenable SettingsService chrome tests, on B2G only; a=TEST-ONLY (aa8c416f75) - Bug 1196358 - update volume setting to database when the volume changing. r=sotaro. (5227fca7f7) - let-var (ca6890c85b) - update (9b9ff0ca42) - Bug 1178081 - Make ro.product.manufacturer and ro.product.device available through settings. r=fabrice (fe962dfe41) - Bug 1206741 - [OTA] Make URL pref changes overwrite B2G's setting. r=fabrice f=gerard-majax (df8bf913ae) - Bug 1196884 - Disable device discovery (again) r=gerard-majax (9f11b3fa9d) - Bug 1178512 - [Metrics] Make the developer HUD send Advanced Telemetry events. r=janx (ddc3643e4e) - Bug 1177143 - Throttle HUD memory collection to 2 seconds. r=jryans (94fea375a8) - Bug 1190622 - [Telemetry] Report USS memory at time of performance marks r=jryans (fd7713fda0) - missing of Bug 1183101 - Collect Histogram Data from Gecko and send to Telemetry Server. r=janx (176b076539) - Bug 1176992 - Allow hud.js to support custom metrics with exponential and counter histograms. r=janx (c536b35be1) - Bug 1198517 - [Metrics] Histogram support for user-timing-based metrics. r=janx (549741a1e4) - Bug 1180793 - [Metrics] Hud should report specific app for those Apps housed under 'Communications'. r=janx (93407f1873) - Bug 1189871 - Add event to add/remove permissions for Graphene. r=fabrice (004144b5ba) - let-var (8c98d39873) - Bug 1160923 - [B2G] Waiting for explicit mozContentEvent before sending out mozChromeEvents, r=vingtetun, f=ochaumeau (63fc3b5ea8) - missing bits of 1177143 and cleanup (339e589ba1) - Bug 1188762 - Use mozSystemWindowChromeEvent to instead of mozChromeEvent to send system-audiochannel-state-changed event. r=alwu (d107f0caab) - Bug 1192122 - Safe mode startup, Part 1: check the power key state at startup r=mwu (90af9b56c0) - Bug 1192122 - Safe mode startup, Part 2: shell.js hook r=ferjm (02676818ff) - Bug 1156715 - Create shell-remote for launching system-remote, r=fabrice (43b18841c9) - missing of Bug 973933 - New updater-xpcshell binary for updater tests. (e6f8cd734a) - Bug 1178760: Add ability to read ua-update.json from APK for Fennec. r=jchen (649d110fdf) - bits of Bug 1076314 - Re-prompt nightly users to enable e10s. (5530f26d51) - Bug 1167601 - Convert newTab.xul to newTab.xhtml. r=mconley (659c19a586) - Bug 1040369 - Replace sponsored icon with identifying text [r=adw] (6ce29de713) - Bug 1192924: Expose the update URL formatting code a new UpdateUtils module. r=rstrong (f2bc4ba6a7) - some of the newer icons (bf3c415d72) - various cleanup and missing bits of Backed out changeset 84da123a0af0 (bug 895359) (d7feb9e535) - bits of Bug 1158853 (02223f32ee) - Bug 1196437 - Moving a sponsored tile in newtab breaks various things [r=marcosc] (0c93d54931) - Bug 1203787: When the add-on ID is longer than 64 characters compare the signing certificate's common name to the sha256 hash of the ID. r=dveditz (838d6ce69c) - bits of Bug 1036284 - Update styling of newtab tiles (886f7d2b71) - let-var (e0ed7d020c) - bug 890026 - Add mozcrash.kill_and_get_minidump r=jimm (11fe69e302) - Bug 1162115 - Bump mozdevice to 0.45, r=wlach (4b5891b54e) - Bug 1140145 - Update to latest wptrunner, a=testonly (a7860252bd) - Bug 1146321 - Update to latest wptrunner, a=testonly (c81ea8eddd) - Bug 115107 - Update to version of wptrunner that allows prefs to be set, r=Ms2ger, ahal (972f8c55bd) - Bug 1154691 - Update wptrunner to remove old exception type, a=testonly (f96ce919b1) - Bug 1150821 - Update to latest wptrunner, a=testonly (bea754f26c) - Bug 1153521 - Update to latest wptrunner, a=testonly (830e395995) - Bug 1155079 -Update to latest wptrunner, a=testonly (27cfd9c8a5) - Bug 1163709 - Update to latest wptrunner, a=testonly (76672ace86) - Bug 1160085 - Update to latest wptrunner, a=testonly (168e165a26) - Bug 1157218 - Update to latest wptrunner, a=testonly (d9eabd0213) - Bug 1171755 - Update to latest wptrunner, a=testonly (4f5b411410) - Bug 1139407 - [mozversion] Remove non-text formatters from command line log options. r=dhunt (32f7596553) - Bug 1160087 - [moznetwork] Add command line interface. r=wlach (2efccde362) - Bug 1175101 - [moznetwork] Bump version number to 0.25. r=wlachance (c8a0fa9ff2) - Bug 1176677 - [moznetwork] ImportError: "cannot import name structured", and release version 0.26. r=davehunt (009449ec79) - Bug 1160090 - [moznetwork] Add structured logging. r=wlach (05a7bc26bc) - Bug 1160094 - [moznetwork] Attempt to pick most suitable IP when multiple are associated with the hostname. r=wlach (db464651e1) - Bug 1163992 - [moznetwork] When multiple IPs are found on Windows pick the first one. r=wlachance (88207df55a) - Bug 1146292 - [mozlog] Bump version to 2.11. r=jgraham (3f9e252ac4) - Bug 1171032 - Log raw messages at debug level by default, r=chmanchester (1ac8fa11ff) - Bg 1171849 Let consumers override mozlog default formatter options, r=chmanchester (beb37921ca) - Bug 1066643 - [mozlog] Allow users of mozlog's command line options to exclude inappropriate log types. r=jgraham (8f4758a6c0) - Bug 1132409 - [mozlog] Create directories for log specified on the command line if not present. r=jgraham (2bda47bd25) - Bug 1177630 - Add formatter to mozlog for producing a machine readable error summary, r=chmanchester (a5930babb0) - Bug 1173380 - [mozprofile] cloned profiles are not cleaned (__del__ method is not called); r=ahal (9d7d931dbf) - Bug 1173682 - [mozbase] tests do not remove created directories; r=ahal (a4b3c112ab) - Bug 1161198 - Update mozdevice test for getLogcat; r=bc (10be1b1a85) - Bug 1014760 - Move mozlog.structured to mozlog; Move mozlog to mozlog.unstructured, r=jgraham (8c1eba0f64) - Bug 1176408 - Bump marionette-transport to 0.5 and marionette-driver to 0.9, r=dburns (fae313803a) - Bug 1178778 - Bump marionette-driver to 0.10. r=automatedtester DONTBUILD (4196568a38) - Bug 1183157 - make marionette --version flag also show the transport and driver package versions. r=dburns (e068536c58) - Bug 1189027: Bump marionette driver to 0.11; r=ato (b1d103c9ef) - Bug 1188826: Bump marionette client version for release; r=ato (018809aae3) - Bug 1176882 - Don't pin marionette-transport. r=davehunt (c660f3ab7b) - Bug 1169381 - Bump dependency for mozinfo in marionette-client to >=0.8. r=jgriffin (a6c0edc9bf) - Bug 1190817 - marionette install from pypi is broken. r=automatedtester (c43fbcfaee) - Bug 1163801 - Upgrade marionette-client from optparse to argparse, r=ahal (5843864209) - Bug 1163801 - Refactor marionette's options mixin system for argparse compatibility, r=AutomatedTester (c8153ebde4) - Bug 1197835 - Version bump marionette-client == 0.19, marionette-transport == 0.7, marionette-driver == 0.13, r=ato (4d7a79ae9a) - Bug 1200973 - Remove unneeded app cache code from Marionette; r=jgriffin (0c2792e2a5) - Bug 1194923 - Call glFlush before glDeleteFramebuffers on Adreno 420 devices. r=snorp (cc1597d3f1) - Bug 1150668 - Assume EXT_texture_format_BGRA8888 supported on Android emulator; r=jgilbert (d6e1884d28) - Bug 1140459 - Skip IsRenderbuffer assertions on Android emulator; r=jgilbert (2163e7c66b) - Bug 683218 - Disable __noSuchMethod__ support. r=jorendorff (b00cc0c8ae) - Bug 1206168 - Rename JS_DefaultValue to JS::ToPrimitive. r=jandem. (0815b56474) - pointer style (d5a0b0f1cc) - Bug 1133191 - Part 0: Add indentation variant to JS::BuildStackString. r=jandem (2383f33b84) - Bug 1133191 - Part 1: Display JS stack trace on exception in js shell. r=jandem,jorendorff (5000cb3ec8) - fix patch order (a84ffb1d33)
@@ -1097,3 +1097,8 @@ pref("dom.bluetooth.app-origin", "app://bluetooth.gaiamobile.org");
|
||||
|
||||
// Enable notification of performance timing
|
||||
pref("dom.performance.enable_notify_performance_timing", true);
|
||||
|
||||
// Multi-screen
|
||||
pref("b2g.multiscreen.chrome_remote_url", "chrome://b2g/content/shell_remote.html");
|
||||
pref("b2g.multiscreen.system_remote_url", "index_remote.html");
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
let Cu = Components.utils;
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
|
||||
dump("############ ErrorPage.js\n");
|
||||
|
||||
let ErrorPageHandler = {
|
||||
var ErrorPageHandler = {
|
||||
_reload: function() {
|
||||
docShell.QueryInterface(Ci.nsIWebNavigation).reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
|
||||
},
|
||||
|
||||
@@ -68,11 +68,11 @@ var developerHUD = {
|
||||
* observed metrics with `target.register(metric)`, and keep them up-to-date
|
||||
* with `target.update(metric, message)` when necessary.
|
||||
*/
|
||||
registerWatcher: function dwp_registerWatcher(watcher) {
|
||||
registerWatcher(watcher) {
|
||||
this._watchers.unshift(watcher);
|
||||
},
|
||||
|
||||
init: function dwp_init() {
|
||||
init() {
|
||||
if (this._client) {
|
||||
return;
|
||||
}
|
||||
@@ -107,9 +107,13 @@ var developerHUD = {
|
||||
SettingsListener.observe('hud.logging', this._logging, enabled => {
|
||||
this._logging = enabled;
|
||||
});
|
||||
|
||||
SettingsListener.observe('debug.performance_data.advanced_telemetry', this._telemetry, enabled => {
|
||||
this._telemetry = enabled;
|
||||
});
|
||||
},
|
||||
|
||||
uninit: function dwp_uninit() {
|
||||
uninit() {
|
||||
if (!this._client) {
|
||||
return;
|
||||
}
|
||||
@@ -128,7 +132,7 @@ var developerHUD = {
|
||||
* This method will ask all registered watchers to track and update metrics
|
||||
* on an app frame.
|
||||
*/
|
||||
trackFrame: function dwp_trackFrame(frame) {
|
||||
trackFrame(frame) {
|
||||
if (this._targets.has(frame)) {
|
||||
return;
|
||||
}
|
||||
@@ -143,7 +147,7 @@ var developerHUD = {
|
||||
});
|
||||
},
|
||||
|
||||
untrackFrame: function dwp_untrackFrame(frame) {
|
||||
untrackFrame(frame) {
|
||||
let target = this._targets.get(frame);
|
||||
if (target) {
|
||||
for (let w of this._watchers) {
|
||||
@@ -155,7 +159,7 @@ var developerHUD = {
|
||||
}
|
||||
},
|
||||
|
||||
onFrameCreated: function (frame, isFirstAppFrame) {
|
||||
onFrameCreated(frame, isFirstAppFrame) {
|
||||
let mozapp = frame.getAttribute('mozapp');
|
||||
if (!mozapp) {
|
||||
return;
|
||||
@@ -163,7 +167,7 @@ var developerHUD = {
|
||||
this.trackFrame(frame);
|
||||
},
|
||||
|
||||
onFrameDestroyed: function (frame, isLastAppFrame) {
|
||||
onFrameDestroyed(frame, isLastAppFrame) {
|
||||
let mozapp = frame.getAttribute('mozapp');
|
||||
if (!mozapp) {
|
||||
return;
|
||||
@@ -171,7 +175,7 @@ var developerHUD = {
|
||||
this.untrackFrame(frame);
|
||||
},
|
||||
|
||||
log: function dwp_log(message) {
|
||||
log(message) {
|
||||
if (this._logging) {
|
||||
dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n');
|
||||
}
|
||||
@@ -189,14 +193,54 @@ function Target(frame, actor) {
|
||||
this.frame = frame;
|
||||
this.actor = actor;
|
||||
this.metrics = new Map();
|
||||
this._appName = null;
|
||||
}
|
||||
|
||||
Target.prototype = {
|
||||
|
||||
get manifest() {
|
||||
return this.frame.appManifestURL;
|
||||
},
|
||||
|
||||
get appName() {
|
||||
|
||||
if (this._appName) {
|
||||
return this._appName;
|
||||
}
|
||||
|
||||
let manifest = this.manifest;
|
||||
if (!manifest) {
|
||||
let msg = DEVELOPER_HUD_LOG_PREFIX + ': Unable to determine app for telemetry metric. src: ' +
|
||||
this.frame.src;
|
||||
console.error(msg);
|
||||
return null;
|
||||
}
|
||||
|
||||
// "communications" apps are a special case
|
||||
if (manifest.indexOf('communications') === -1) {
|
||||
let start = manifest.indexOf('/') + 2;
|
||||
let end = manifest.indexOf('.', start);
|
||||
this._appName = manifest.substring(start, end).toLowerCase();
|
||||
} else {
|
||||
let src = this.frame.src;
|
||||
if (src) {
|
||||
// e.g., `app://communications.gaiamobile.org/contacts/index.html`
|
||||
let parts = src.split('/');
|
||||
let APP = 3;
|
||||
let EXPECTED_PARTS_LENGTH = 5;
|
||||
if (parts.length === EXPECTED_PARTS_LENGTH) {
|
||||
this._appName = parts[APP];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._appName;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a metric that can later be updated. Does not update the front-end.
|
||||
*/
|
||||
register: function target_register(metric) {
|
||||
register(metric) {
|
||||
this.metrics.set(metric, 0);
|
||||
},
|
||||
|
||||
@@ -204,7 +248,7 @@ Target.prototype = {
|
||||
* Modify one of a target's metrics, and send out an event to notify relevant
|
||||
* parties (e.g. the developer HUD, automated tests, etc).
|
||||
*/
|
||||
update: function target_update(metric, message) {
|
||||
update(metric, message) {
|
||||
if (!metric.name) {
|
||||
throw new Error('Missing metric.name');
|
||||
}
|
||||
@@ -220,7 +264,7 @@ Target.prototype = {
|
||||
|
||||
let data = {
|
||||
metrics: [], // FIXME(Bug 982066) Remove this field.
|
||||
manifest: this.frame.appManifestURL,
|
||||
manifest: this.manifest,
|
||||
metric: metric,
|
||||
message: message
|
||||
};
|
||||
@@ -235,6 +279,7 @@ Target.prototype = {
|
||||
if (message) {
|
||||
developerHUD.log('[' + data.manifest + '] ' + data.message);
|
||||
}
|
||||
|
||||
this._send(data);
|
||||
},
|
||||
|
||||
@@ -242,7 +287,7 @@ Target.prototype = {
|
||||
* Nicer way to call update() when the metric value is a number that needs
|
||||
* to be incremented.
|
||||
*/
|
||||
bump: function target_bump(metric, message) {
|
||||
bump(metric, message) {
|
||||
metric.value = (this.metrics.get(metric.name) || 0) + 1;
|
||||
this.update(metric, message);
|
||||
},
|
||||
@@ -251,7 +296,7 @@ Target.prototype = {
|
||||
* Void a metric value and make sure it isn't displayed on the front-end
|
||||
* anymore.
|
||||
*/
|
||||
clear: function target_clear(metric) {
|
||||
clear(metric) {
|
||||
metric.value = 0;
|
||||
this.update(metric);
|
||||
},
|
||||
@@ -260,22 +305,120 @@ Target.prototype = {
|
||||
* Tear everything down, including the front-end by sending a message without
|
||||
* widgets.
|
||||
*/
|
||||
destroy: function target_destroy() {
|
||||
destroy() {
|
||||
delete this.metrics;
|
||||
this._send({});
|
||||
this._send({metric: {skipTelemetry: true}});
|
||||
},
|
||||
|
||||
_send: function target_send(data) {
|
||||
_send(data) {
|
||||
let frame = this.frame;
|
||||
|
||||
let systemapp = document.querySelector('#systemapp');
|
||||
if (this.frame === systemapp) {
|
||||
frame = getContentWindow();
|
||||
shell.sendEvent(frame, 'developer-hud-update', Cu.cloneInto(data, frame));
|
||||
this._logHistogram(data.metric);
|
||||
},
|
||||
|
||||
_getAddonHistogram(item) {
|
||||
let APPNAME_IDX = 3;
|
||||
let HISTNAME_IDX = 4;
|
||||
|
||||
let array = item.split('_');
|
||||
let appName = array[APPNAME_IDX].toUpperCase();
|
||||
let histName = array[HISTNAME_IDX].toUpperCase();
|
||||
return Services.telemetry.getAddonHistogram(appName,
|
||||
CUSTOM_HISTOGRAM_PREFIX + histName);
|
||||
},
|
||||
|
||||
_clearTelemetryData() {
|
||||
developerHUD._histograms.forEach(function(item) {
|
||||
Services.telemetry.getKeyedHistogramById(item).clear();
|
||||
});
|
||||
|
||||
developerHUD._customHistograms.forEach(item => {
|
||||
this._getAddonHistogram(item).clear();
|
||||
});
|
||||
},
|
||||
|
||||
_sendTelemetryData() {
|
||||
if (!developerHUD._telemetry) {
|
||||
return;
|
||||
}
|
||||
telemetryDebug('calling sendTelemetryData');
|
||||
let frame = this.frame;
|
||||
let payload = {
|
||||
keyedHistograms: {},
|
||||
addonHistograms: {}
|
||||
};
|
||||
// Package the hud histograms.
|
||||
developerHUD._histograms.forEach(function(item) {
|
||||
payload.keyedHistograms[item] =
|
||||
Services.telemetry.getKeyedHistogramById(item).snapshot();
|
||||
});
|
||||
// Package the registered hud custom histograms
|
||||
developerHUD._customHistograms.forEach(item => {
|
||||
payload.addonHistograms[item] = this._getAddonHistogram(item).snapshot();
|
||||
});
|
||||
|
||||
shell.sendEvent(frame, 'advanced-telemetry-update', Cu.cloneInto(payload, frame));
|
||||
},
|
||||
|
||||
_logHistogram(metric) {
|
||||
if (!developerHUD._telemetry || metric.skipTelemetry) {
|
||||
return;
|
||||
}
|
||||
|
||||
shell.sendEvent(frame, 'developer-hud-update', Cu.cloneInto(data, frame));
|
||||
}
|
||||
metric.appName = this.appName;
|
||||
if (!metric.appName) {
|
||||
return;
|
||||
}
|
||||
|
||||
let metricName = metric.name.toUpperCase();
|
||||
let metricAppName = metric.appName.toUpperCase();
|
||||
if (!metric.custom) {
|
||||
let keyedMetricName = 'DEVTOOLS_HUD_' + metricName;
|
||||
try {
|
||||
let keyed = Services.telemetry.getKeyedHistogramById(keyedMetricName);
|
||||
if (keyed) {
|
||||
keyed.add(metric.appName, parseInt(metric.value, 10));
|
||||
developerHUD._histograms.add(keyedMetricName);
|
||||
telemetryDebug(keyedMetricName, metric.value, metric.appName);
|
||||
}
|
||||
} catch(err) {
|
||||
console.error('Histogram error is metricname added to histograms.json:'
|
||||
+ keyedMetricName);
|
||||
}
|
||||
} else {
|
||||
let histogramName = CUSTOM_HISTOGRAM_PREFIX + metricAppName + '_'
|
||||
+ metricName;
|
||||
// This is a call to add a value to an existing histogram.
|
||||
if (typeof metric.value !== 'undefined') {
|
||||
Services.telemetry.getAddonHistogram(metricAppName,
|
||||
CUSTOM_HISTOGRAM_PREFIX + metricName).add(parseInt(metric.value, 10));
|
||||
telemetryDebug(histogramName, metric.value);
|
||||
return;
|
||||
}
|
||||
|
||||
// The histogram already exists and are not adding data to it.
|
||||
if (developerHUD._customHistograms.has(histogramName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a call to create a new histogram.
|
||||
try {
|
||||
let metricType = parseInt(metric.type, 10);
|
||||
if (metricType === Services.telemetry.HISTOGRAM_COUNT) {
|
||||
Services.telemetry.registerAddonHistogram(metricAppName,
|
||||
CUSTOM_HISTOGRAM_PREFIX + metricName, metricType);
|
||||
} else {
|
||||
Services.telemetry.registerAddonHistogram(metricAppName,
|
||||
CUSTOM_HISTOGRAM_PREFIX + metricName, metricType, metric.min,
|
||||
metric.max, metric.buckets);
|
||||
}
|
||||
developerHUD._customHistograms.add(histogramName);
|
||||
} catch (err) {
|
||||
console.error('Histogram error: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -283,7 +426,7 @@ Target.prototype = {
|
||||
* The Console Watcher tracks the following metrics in apps: reflows, warnings,
|
||||
* and errors, with security errors reported separately.
|
||||
*/
|
||||
let consoleWatcher = {
|
||||
var consoleWatcher = {
|
||||
|
||||
_client: null,
|
||||
_targets: new Map(),
|
||||
@@ -304,7 +447,7 @@ let consoleWatcher = {
|
||||
'CORS'
|
||||
],
|
||||
|
||||
init: function cw_init(client) {
|
||||
init(client) {
|
||||
this._client = client;
|
||||
this.consoleListener = this.consoleListener.bind(this);
|
||||
|
||||
@@ -331,7 +474,7 @@ let consoleWatcher = {
|
||||
client.addListener('reflowActivity', this.consoleListener);
|
||||
},
|
||||
|
||||
trackTarget: function cw_trackTarget(target) {
|
||||
trackTarget(target) {
|
||||
target.register('reflows');
|
||||
target.register('warnings');
|
||||
target.register('errors');
|
||||
@@ -346,7 +489,7 @@ let consoleWatcher = {
|
||||
});
|
||||
},
|
||||
|
||||
untrackTarget: function cw_untrackTarget(target) {
|
||||
untrackTarget(target) {
|
||||
this._client.request({
|
||||
to: target.actor.consoleActor,
|
||||
type: 'stopListeners',
|
||||
@@ -356,7 +499,7 @@ let consoleWatcher = {
|
||||
this._targets.delete(target.actor.consoleActor);
|
||||
},
|
||||
|
||||
consoleListener: function cw_consoleListener(type, packet) {
|
||||
consoleListener(type, packet) {
|
||||
let target = this._targets.get(packet.from);
|
||||
let metric = {};
|
||||
let output = '';
|
||||
@@ -376,6 +519,18 @@ let consoleWatcher = {
|
||||
|
||||
if (this._security.indexOf(pageError.category) > -1) {
|
||||
metric.name = 'security';
|
||||
|
||||
// Telemetry sends the security error category not the
|
||||
// count of security errors.
|
||||
target._logHistogram({
|
||||
name: 'security_category',
|
||||
value: pageError.category
|
||||
});
|
||||
|
||||
// Indicate that the 'hud' security metric (the count of security
|
||||
// errors) should not be sent as a telemetry metric since the
|
||||
// security error category is being sent instead.
|
||||
metric.skipTelemetry = true;
|
||||
}
|
||||
|
||||
let {errorMessage, sourceName, category, lineNumber, columnNumber} = pageError;
|
||||
@@ -459,7 +614,7 @@ let consoleWatcher = {
|
||||
target.bump(metric, output);
|
||||
},
|
||||
|
||||
formatSourceURL: function cw_formatSourceURL(packet) {
|
||||
formatSourceURL(packet) {
|
||||
// Abbreviate source URL
|
||||
let source = WebConsoleUtils.abbreviateSourceURL(packet.sourceURL);
|
||||
|
||||
@@ -469,6 +624,65 @@ let consoleWatcher = {
|
||||
', ' + source + ':' + sourceLine;
|
||||
|
||||
return source;
|
||||
},
|
||||
|
||||
handleTelemetryMessage(target, packet) {
|
||||
if (!developerHUD._telemetry) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is a 'telemetry' log entry, create a telemetry metric from
|
||||
// the log content.
|
||||
let separator = '|';
|
||||
let logContent = packet.message.arguments.toString();
|
||||
|
||||
if (logContent.indexOf('telemetry') < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let telemetryData = logContent.split(separator);
|
||||
|
||||
// Positions of the components of a telemetry log entry.
|
||||
let TELEMETRY_IDENTIFIER_IDX = 0;
|
||||
let NAME_IDX = 1;
|
||||
let VALUE_IDX = 2;
|
||||
let TYPE_IDX = 2;
|
||||
let MIN_IDX = 3;
|
||||
let MAX_IDX = 4;
|
||||
let BUCKETS_IDX = 5;
|
||||
let MAX_CUSTOM_ARGS = 6;
|
||||
let MIN_CUSTOM_ARGS = 3;
|
||||
|
||||
if (telemetryData[TELEMETRY_IDENTIFIER_IDX] != 'telemetry' ||
|
||||
telemetryData.length < MIN_CUSTOM_ARGS ||
|
||||
telemetryData.length > MAX_CUSTOM_ARGS) {
|
||||
return;
|
||||
}
|
||||
|
||||
let metric = {
|
||||
name: telemetryData[NAME_IDX]
|
||||
};
|
||||
|
||||
if (metric.name === 'MGMT') {
|
||||
metric.value = telemetryData[VALUE_IDX];
|
||||
if (metric.value === 'TIMETOSHIP') {
|
||||
telemetryDebug('Received a Ship event');
|
||||
target._sendTelemetryData();
|
||||
} else if (metric.value === 'CLEARMETRICS') {
|
||||
target._clearTelemetryData();
|
||||
}
|
||||
} else {
|
||||
if (telemetryData.length === MIN_CUSTOM_ARGS) {
|
||||
metric.value = telemetryData[VALUE_IDX];
|
||||
} else if (telemetryData.length === MAX_CUSTOM_ARGS) {
|
||||
metric.type = telemetryData[TYPE_IDX];
|
||||
metric.min = telemetryData[MIN_IDX];
|
||||
metric.max = telemetryData[MAX_IDX];
|
||||
metric.buckets = telemetryData[BUCKETS_IDX];
|
||||
}
|
||||
metric.custom = true;
|
||||
target._logHistogram(metric);
|
||||
}
|
||||
}
|
||||
};
|
||||
developerHUD.registerWatcher(consoleWatcher);
|
||||
@@ -479,13 +693,13 @@ var eventLoopLagWatcher = {
|
||||
_fronts: new Map(),
|
||||
_active: false,
|
||||
|
||||
init: function(client) {
|
||||
init(client) {
|
||||
this._client = client;
|
||||
|
||||
SettingsListener.observe('hud.jank', false, this.settingsListener.bind(this));
|
||||
},
|
||||
|
||||
settingsListener: function(value) {
|
||||
settingsListener(value) {
|
||||
if (this._active == value) {
|
||||
return;
|
||||
}
|
||||
@@ -504,7 +718,7 @@ var eventLoopLagWatcher = {
|
||||
}
|
||||
},
|
||||
|
||||
trackTarget: function(target) {
|
||||
trackTarget(target) {
|
||||
target.register('jank');
|
||||
|
||||
let front = new EventLoopLagFront(this._client, target.actor);
|
||||
@@ -519,7 +733,7 @@ var eventLoopLagWatcher = {
|
||||
}
|
||||
},
|
||||
|
||||
untrackTarget: function(target) {
|
||||
untrackTarget(target) {
|
||||
let fronts = this._fronts;
|
||||
if (fronts.has(target)) {
|
||||
fronts.get(target).destroy();
|
||||
@@ -544,9 +758,24 @@ var performanceEntriesWatcher = {
|
||||
_fronts: new Map(),
|
||||
_appLaunchName: null,
|
||||
_appLaunchStartTime: null,
|
||||
_supported: [
|
||||
'contentInteractive',
|
||||
'navigationInteractive',
|
||||
'navigationLoaded',
|
||||
'visuallyLoaded',
|
||||
'fullyLoaded',
|
||||
'mediaEnumerated',
|
||||
'scanEnd'
|
||||
],
|
||||
|
||||
init(client) {
|
||||
this._client = client;
|
||||
let setting = 'devtools.telemetry.supported_performance_marks';
|
||||
let defaultValue = this._supported.join(',');
|
||||
|
||||
SettingsListener.observe(setting, defaultValue, supported => {
|
||||
this._supported = supported.split(',');
|
||||
});
|
||||
},
|
||||
|
||||
trackTarget(target) {
|
||||
@@ -562,31 +791,56 @@ var performanceEntriesWatcher = {
|
||||
front.start();
|
||||
|
||||
front.on('entry', detail => {
|
||||
if (detail.type === 'mark') {
|
||||
let name = detail.name;
|
||||
let epoch = detail.epoch;
|
||||
let CHARS_UNTIL_APP_NAME = 7; // '@app://'
|
||||
|
||||
// FIXME There is a potential race condition that can result
|
||||
// in some performance entries being disregarded. See bug 1189942.
|
||||
if (name.indexOf('appLaunch') != -1) {
|
||||
let appStartPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME;
|
||||
let length = (name.indexOf('.') - appStartPos);
|
||||
this._appLaunchName = name.substr(appStartPos, length);
|
||||
this._appLaunchStartTime = epoch;
|
||||
} else {
|
||||
let origin = detail.origin;
|
||||
origin = origin.substr(0, origin.indexOf('.'));
|
||||
if (this._appLaunchName === origin) {
|
||||
let time = epoch - this._appLaunchStartTime;
|
||||
let eventName = 'app-startup-time-' + name;
|
||||
|
||||
// Events based on performance marks are for telemetry only, they are
|
||||
// not displayed in the HUD front end.
|
||||
target._sendTelemetryEvent({name: eventName, value: time});
|
||||
}
|
||||
}
|
||||
// Only process performance marks.
|
||||
if (detail.type !== 'mark') {
|
||||
return;
|
||||
}
|
||||
|
||||
let name = detail.name;
|
||||
let epoch = detail.epoch;
|
||||
|
||||
// FIXME There is a potential race condition that can result
|
||||
// in some performance entries being disregarded. See bug 1189942.
|
||||
//
|
||||
// If this is an "app launch" mark, record the app that was
|
||||
// launched and the epoch of when it was launched.
|
||||
if (name.indexOf('appLaunch') !== -1) {
|
||||
let CHARS_UNTIL_APP_NAME = 7; // '@app://'
|
||||
let startPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME;
|
||||
let endPos = name.indexOf('.');
|
||||
this._appLaunchName = name.slice(startPos, endPos);
|
||||
this._appLaunchStartTime = epoch;
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process supported performance marks
|
||||
if (this._supported.indexOf(name) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let origin = detail.origin;
|
||||
origin = origin.slice(0, origin.indexOf('.'));
|
||||
|
||||
// Continue if the performance mark corresponds to the app
|
||||
// for which we have recorded app launch information.
|
||||
if (this._appLaunchName !== origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
let time = epoch - this._appLaunchStartTime;
|
||||
let eventName = 'app_startup_time_' + name;
|
||||
|
||||
// Events based on performance marks are for telemetry only, they are
|
||||
// not displayed in the HUD front end.
|
||||
target._logHistogram({name: eventName, value: time});
|
||||
|
||||
memoryWatcher.front(target).residentUnique().then(value => {
|
||||
eventName = 'app_memory_' + name;
|
||||
target._logHistogram({name: eventName, value: value});
|
||||
}, err => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -620,7 +874,7 @@ var memoryWatcher = {
|
||||
},
|
||||
_active: false,
|
||||
|
||||
init: function mw_init(client) {
|
||||
init(client) {
|
||||
this._client = client;
|
||||
let watching = this._watching;
|
||||
|
||||
@@ -633,7 +887,7 @@ var memoryWatcher = {
|
||||
}
|
||||
},
|
||||
|
||||
update: function mw_update() {
|
||||
update() {
|
||||
let watching = this._watching;
|
||||
let active = watching.appmemory || watching.uss;
|
||||
|
||||
@@ -651,13 +905,12 @@ var memoryWatcher = {
|
||||
this._active = active;
|
||||
},
|
||||
|
||||
measure: function mw_measure(target) {
|
||||
measure(target) {
|
||||
let watch = this._watching;
|
||||
let front = this._fronts.get(target);
|
||||
let format = this.formatMemory;
|
||||
|
||||
if (watch.uss) {
|
||||
front.residentUnique().then(value => {
|
||||
this.front(target).residentUnique().then(value => {
|
||||
target.update({name: 'uss', value: value}, 'USS: ' + format(value));
|
||||
}, err => {
|
||||
console.error(err);
|
||||
@@ -694,11 +947,11 @@ var memoryWatcher = {
|
||||
});
|
||||
}
|
||||
|
||||
let timer = setTimeout(() => this.measure(target), 800);
|
||||
let timer = setTimeout(() => this.measure(target), 2000);
|
||||
this._timers.set(target, timer);
|
||||
},
|
||||
|
||||
formatMemory: function mw_formatMemory(bytes) {
|
||||
formatMemory(bytes) {
|
||||
var prefix = ['','K','M','G','T','P','E','Z','Y'];
|
||||
var i = 0;
|
||||
for (; bytes > 1024 && i < prefix.length; ++i) {
|
||||
@@ -707,7 +960,7 @@ var memoryWatcher = {
|
||||
return (Math.round(bytes * 100) / 100) + ' ' + prefix[i] + 'B';
|
||||
},
|
||||
|
||||
trackTarget: function mw_trackTarget(target) {
|
||||
trackTarget(target) {
|
||||
target.register('uss');
|
||||
target.register('memory');
|
||||
this._fronts.set(target, MemoryFront(this._client, target.actor));
|
||||
@@ -716,7 +969,7 @@ var memoryWatcher = {
|
||||
}
|
||||
},
|
||||
|
||||
untrackTarget: function mw_untrackTarget(target) {
|
||||
untrackTarget(target) {
|
||||
let front = this._fronts.get(target);
|
||||
if (front) {
|
||||
front.destroy();
|
||||
@@ -724,6 +977,10 @@ var memoryWatcher = {
|
||||
this._fronts.delete(target);
|
||||
this._timers.delete(target);
|
||||
}
|
||||
},
|
||||
|
||||
front(target) {
|
||||
return this._fronts.get(target);
|
||||
}
|
||||
};
|
||||
developerHUD.registerWatcher(memoryWatcher);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// runapp.js:
|
||||
// Provide a --runapp APPNAME command-line option.
|
||||
|
||||
let runAppObj;
|
||||
var runAppObj;
|
||||
window.addEventListener('load', function() {
|
||||
// Get the command line arguments that were passed to the b2g client
|
||||
let args;
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
// TODO: support multiple device pixels per CSS pixel
|
||||
//
|
||||
|
||||
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
let isMulet = "ResponsiveUI" in browserWindow;
|
||||
var browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
var isMulet = "ResponsiveUI" in browserWindow;
|
||||
Cu.import("resource://gre/modules/GlobalSimulatorScreen.jsm");
|
||||
|
||||
// We do this on ContentStart because querying the displayDPI fails otherwise.
|
||||
window.addEventListener('ContentStart', function() {
|
||||
window.addEventListener('ContentStart', onStart);
|
||||
window.addEventListener('SafeModeStart', onStart);
|
||||
|
||||
// We do this on ContentStart and SafeModeStart because querying the
|
||||
// displayDPI fails otherwise.
|
||||
function onStart() {
|
||||
// This is the toplevel <window> element
|
||||
let shell = document.getElementById('shell');
|
||||
|
||||
@@ -255,4 +259,4 @@ window.addEventListener('ContentStart', function() {
|
||||
// Exit the b2g client
|
||||
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,6 +78,12 @@ var SettingsListener = {
|
||||
|
||||
SettingsListener.init();
|
||||
|
||||
// =================== Mono Audio ======================
|
||||
|
||||
SettingsListener.observe('accessibility.monoaudio.enable', false, function(value) {
|
||||
Services.prefs.setBoolPref('accessibility.monoaudio.enable', value);
|
||||
});
|
||||
|
||||
// =================== Console ======================
|
||||
|
||||
SettingsListener.observe('debug.console.enabled', true, function(value) {
|
||||
@@ -149,12 +155,16 @@ Components.utils.import('resource://gre/modules/ctypes.jsm');
|
||||
// Get the hardware info and firmware revision from device properties.
|
||||
let hardware_info = null;
|
||||
let firmware_revision = null;
|
||||
let product_manufacturer = null;
|
||||
let product_model = null;
|
||||
let product_device = null;
|
||||
let build_number = null;
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
hardware_info = libcutils.property_get('ro.hardware');
|
||||
firmware_revision = libcutils.property_get('ro.firmware_revision');
|
||||
product_manufacturer = libcutils.property_get('ro.product.manufacturer');
|
||||
product_model = libcutils.property_get('ro.product.model');
|
||||
product_device = libcutils.property_get('ro.product.device');
|
||||
build_number = libcutils.property_get('ro.build.version.incremental');
|
||||
#endif
|
||||
|
||||
@@ -174,7 +184,9 @@ Components.utils.import('resource://gre/modules/ctypes.jsm');
|
||||
'deviceinfo.platform_build_id': appInfo.platformBuildID,
|
||||
'deviceinfo.hardware': hardware_info,
|
||||
'deviceinfo.firmware_revision': firmware_revision,
|
||||
'deviceinfo.product_model': product_model
|
||||
'deviceinfo.product_manufacturer': product_manufacturer,
|
||||
'deviceinfo.product_model': product_model,
|
||||
'deviceinfo.product_device': product_device
|
||||
}
|
||||
lock.set(setting);
|
||||
}
|
||||
@@ -199,19 +211,28 @@ SettingsListener.observe('devtools.overlay', false, (value) => {
|
||||
});
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
let LogShake;
|
||||
SettingsListener.observe('devtools.logshake', false, (value) => {
|
||||
|
||||
var LogShake;
|
||||
(function() {
|
||||
let scope = {};
|
||||
Cu.import('resource://gre/modules/LogShake.jsm', scope);
|
||||
LogShake = scope.LogShake;
|
||||
LogShake.init();
|
||||
})();
|
||||
|
||||
SettingsListener.observe('devtools.logshake.enabled', false, value => {
|
||||
if (value) {
|
||||
if (!LogShake) {
|
||||
let scope = {};
|
||||
Cu.import('resource://gre/modules/LogShake.jsm', scope);
|
||||
LogShake = scope.LogShake;
|
||||
}
|
||||
LogShake.init();
|
||||
LogShake.enableDeviceMotionListener();
|
||||
} else {
|
||||
if (LogShake) {
|
||||
LogShake.uninit();
|
||||
}
|
||||
LogShake.disableDeviceMotionListener();
|
||||
}
|
||||
});
|
||||
|
||||
SettingsListener.observe('devtools.logshake.qa_enabled', false, value => {
|
||||
if (value) {
|
||||
LogShake.enableQAMode();
|
||||
} else {
|
||||
LogShake.disableQAMode();
|
||||
}
|
||||
});
|
||||
#endif
|
||||
@@ -292,30 +313,48 @@ setUpdateTrackingId();
|
||||
// modify them, that's where we need to make our changes.
|
||||
let defaultBranch = Services.prefs.getDefaultBranch(null);
|
||||
|
||||
function syncCharPref(prefName) {
|
||||
SettingsListener.observe(prefName, null, function(value) {
|
||||
// If set, propagate setting value to pref.
|
||||
if (value) {
|
||||
defaultBranch.setCharPref(prefName, value);
|
||||
function syncPrefDefault(prefName) {
|
||||
// The pref value at boot-time will serve as default for the setting.
|
||||
let defaultValue = defaultBranch.getCharPref(prefName);
|
||||
let defaultSetting = {};
|
||||
defaultSetting[prefName] = defaultValue;
|
||||
|
||||
// We back up that value in order to detect pref changes across reboots.
|
||||
// Such a change can happen e.g. when the user installs an OTA update that
|
||||
// changes the update URL format.
|
||||
let backupName = prefName + '.old';
|
||||
try {
|
||||
// Everything relies on the comparison below: When pushing a new Gecko
|
||||
// that changes app.update.url or app.update.channel, we overwrite any
|
||||
// existing setting with the new pref value.
|
||||
let backupValue = Services.prefs.getCharPref(backupName);
|
||||
if (defaultValue !== backupValue) {
|
||||
// If the pref has changed since our last backup, overwrite the setting.
|
||||
navigator.mozSettings.createLock().set(defaultSetting);
|
||||
}
|
||||
} catch(e) {
|
||||
// There was no backup: Overwrite the setting and create a backup below.
|
||||
navigator.mozSettings.createLock().set(defaultSetting);
|
||||
}
|
||||
|
||||
// Initialize or update the backup value.
|
||||
Services.prefs.setCharPref(backupName, defaultValue);
|
||||
|
||||
// Propagate setting changes to the pref.
|
||||
SettingsListener.observe(prefName, defaultValue, value => {
|
||||
if (!value) {
|
||||
// If the setting value is invalid, reset it to its default.
|
||||
navigator.mozSettings.createLock().set(defaultSetting);
|
||||
return;
|
||||
}
|
||||
// If unset, initialize setting to pref value.
|
||||
try {
|
||||
let value = defaultBranch.getCharPref(prefName);
|
||||
if (value) {
|
||||
let setting = {};
|
||||
setting[prefName] = value;
|
||||
window.navigator.mozSettings.createLock().set(setting);
|
||||
}
|
||||
} catch(e) {
|
||||
console.log('Unable to read pref ' + prefName + ': ' + e);
|
||||
}
|
||||
// Here we will overwrite the pref with the setting value.
|
||||
defaultBranch.setCharPref(prefName, value);
|
||||
});
|
||||
}
|
||||
|
||||
syncCharPref(AppConstants.MOZ_B2GDROID ? 'app.update.url.android'
|
||||
: 'app.update.url');
|
||||
syncCharPref('app.update.channel');
|
||||
syncPrefDefault(AppConstants.MOZ_B2GDROID ? 'app.update.url.android'
|
||||
: 'app.update.url');
|
||||
syncPrefDefault('app.update.channel');
|
||||
})();
|
||||
|
||||
// ================ Debug ================
|
||||
@@ -602,9 +641,13 @@ var settingsToObserve = {
|
||||
'devtools.remote.wifi.visible': {
|
||||
resetToPref: true
|
||||
},
|
||||
'devtools.telemetry.supported_performance_marks': {
|
||||
resetToPref: true
|
||||
},
|
||||
|
||||
'dom.mozApps.use_reviewer_certs': false,
|
||||
'dom.mozApps.signed_apps_installable_from': 'https://marketplace.firefox.com',
|
||||
'dom.presentation.discovery.enabled': true,
|
||||
'dom.presentation.discovery.enabled': false,
|
||||
'dom.presentation.discoverable': false,
|
||||
'dom.serviceWorkers.interception.enabled': true,
|
||||
'dom.serviceWorkers.testing.enabled': false,
|
||||
|
||||
@@ -20,9 +20,11 @@ Cu.import('resource://gre/modules/AlertsHelper.jsm');
|
||||
Cu.import('resource://gre/modules/RequestSyncService.jsm');
|
||||
Cu.import('resource://gre/modules/SystemUpdateService.jsm');
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
Cu.import('resource://gre/modules/MultiscreenHandler.jsm');
|
||||
Cu.import('resource://gre/modules/NetworkStatsService.jsm');
|
||||
Cu.import('resource://gre/modules/ResourceStatsService.jsm');
|
||||
#endif
|
||||
Cu.import('resource://gre/modules/KillSwitchMain.jsm');
|
||||
|
||||
// Identity
|
||||
Cu.import('resource://gre/modules/SignInToWebsite.jsm');
|
||||
@@ -75,11 +77,10 @@ XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector',
|
||||
'@mozilla.org/toolkit/captive-detector;1',
|
||||
'nsICaptivePortalDetector');
|
||||
|
||||
window.performance.measure('gecko-shell-jsm-loaded', 'gecko-shell-loadstart');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SafeMode",
|
||||
"resource://gre/modules/SafeMode.jsm");
|
||||
|
||||
function getContentWindow() {
|
||||
return shell.contentBrowser.contentWindow;
|
||||
}
|
||||
window.performance.measure('gecko-shell-jsm-loaded', 'gecko-shell-loadstart');
|
||||
|
||||
function debug(str) {
|
||||
dump(' -*- Shell.js: ' + str + '\n');
|
||||
@@ -91,7 +92,13 @@ var shell = {
|
||||
|
||||
get CrashSubmit() {
|
||||
delete this.CrashSubmit;
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
|
||||
return this.CrashSubmit;
|
||||
#else
|
||||
dump('Crash reporter : disabled at build time.');
|
||||
return this.CrashSubmit = null;
|
||||
#endif
|
||||
},
|
||||
|
||||
onlineForCrashReport: function shell_onlineForCrashReport() {
|
||||
@@ -224,17 +231,21 @@ var shell = {
|
||||
#endif
|
||||
|
||||
window.performance.mark('gecko-shell-bootstrap');
|
||||
let startManifestURL =
|
||||
Cc['@mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap']
|
||||
.getService(Ci.nsISupports).wrappedJSObject.startManifestURL;
|
||||
if (startManifestURL) {
|
||||
Cu.import('resource://gre/modules/Bootstraper.jsm');
|
||||
Bootstraper.ensureSystemAppInstall(startManifestURL)
|
||||
.then(this.start.bind(this))
|
||||
.catch(Bootstraper.bailout);
|
||||
} else {
|
||||
this.start();
|
||||
}
|
||||
|
||||
// Before anything, check if we want to start in safe mode.
|
||||
SafeMode.check(window).then(() => {
|
||||
let startManifestURL =
|
||||
Cc['@mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap']
|
||||
.getService(Ci.nsISupports).wrappedJSObject.startManifestURL;
|
||||
if (startManifestURL) {
|
||||
Cu.import('resource://gre/modules/Bootstraper.jsm');
|
||||
Bootstraper.ensureSystemAppInstall(startManifestURL)
|
||||
.then(this.start.bind(this))
|
||||
.catch(Bootstraper.bailout);
|
||||
} else {
|
||||
this.start();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
start: function shell_start() {
|
||||
@@ -368,7 +379,7 @@ var shell = {
|
||||
CaptivePortalLoginHelper.init();
|
||||
|
||||
this.contentBrowser.src = homeURL;
|
||||
this.isHomeLoaded = false;
|
||||
this._isEventListenerReady = false;
|
||||
|
||||
window.performance.mark('gecko-shell-system-frame-set');
|
||||
|
||||
@@ -463,6 +474,13 @@ var shell = {
|
||||
this.contentBrowser.setVisible(true);
|
||||
}
|
||||
break;
|
||||
case 'load':
|
||||
if (content.document.location == 'about:blank') {
|
||||
return;
|
||||
}
|
||||
content.removeEventListener('load', this, true);
|
||||
this.notifyContentWindowLoaded();
|
||||
break;
|
||||
case 'mozbrowserloadstart':
|
||||
if (content.document.location == 'about:blank') {
|
||||
this.contentBrowser.addEventListener('mozbrowserlocationchange', this, true);
|
||||
@@ -569,9 +587,12 @@ var shell = {
|
||||
break;
|
||||
case 'MozAfterPaint':
|
||||
window.removeEventListener('MozAfterPaint', this);
|
||||
this.sendChromeEvent({
|
||||
// This event should be sent before System app returns with
|
||||
// system-message-listener-ready mozContentEvent, because it's on
|
||||
// the critical launch path of the app.
|
||||
SystemAppProxy._sendCustomEvent('mozChromeEvent', {
|
||||
type: 'system-first-paint'
|
||||
});
|
||||
}, /* noPending */ true);
|
||||
break;
|
||||
case 'unload':
|
||||
this.stop();
|
||||
@@ -581,7 +602,7 @@ var shell = {
|
||||
// TODO: We should get the `isActive` state from evt.isActive.
|
||||
// Then we don't need to do `channel.isActive()` here.
|
||||
channel.isActive().onsuccess = function(evt) {
|
||||
this.sendChromeEvent({
|
||||
SystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
|
||||
type: 'system-audiochannel-state-changed',
|
||||
name: channel.name,
|
||||
isActive: evt.target.result
|
||||
@@ -593,6 +614,14 @@ var shell = {
|
||||
|
||||
// Send an event to a specific window, document or element.
|
||||
sendEvent: function shell_sendEvent(target, type, details) {
|
||||
if (target === this.contentBrowser) {
|
||||
// We must ask SystemAppProxy to send the event in this case so
|
||||
// that event would be dispatched from frame.contentWindow instead of
|
||||
// on the System app frame.
|
||||
SystemAppProxy._sendCustomEvent(type, details);
|
||||
return;
|
||||
}
|
||||
|
||||
let doc = target.document || target.ownerDocument || target;
|
||||
let event = doc.createEvent('CustomEvent');
|
||||
event.initCustomEvent(type, true, true, details ? details : {});
|
||||
@@ -600,21 +629,10 @@ var shell = {
|
||||
},
|
||||
|
||||
sendCustomEvent: function shell_sendCustomEvent(type, details) {
|
||||
let target = getContentWindow();
|
||||
let payload = details ? Cu.cloneInto(details, target) : {};
|
||||
this.sendEvent(target, type, payload);
|
||||
SystemAppProxy._sendCustomEvent(type, details);
|
||||
},
|
||||
|
||||
sendChromeEvent: function shell_sendChromeEvent(details) {
|
||||
if (!this.isHomeLoaded) {
|
||||
if (!('pendingChromeEvents' in this)) {
|
||||
this.pendingChromeEvents = [];
|
||||
}
|
||||
|
||||
this.pendingChromeEvents.push(details);
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendCustomEvent("mozChromeEvent", details);
|
||||
},
|
||||
|
||||
@@ -655,6 +673,7 @@ var shell = {
|
||||
this.contentBrowser.removeEventListener('mozbrowserlocationchange', this, true);
|
||||
|
||||
let content = this.contentBrowser.contentWindow;
|
||||
content.addEventListener('load', this, true);
|
||||
|
||||
this.reportCrash(true);
|
||||
|
||||
@@ -668,28 +687,7 @@ var shell = {
|
||||
Cu.import('resource://gre/modules/OperatorApps.jsm');
|
||||
#endif
|
||||
|
||||
content.addEventListener('load', function shell_homeLoaded() {
|
||||
content.removeEventListener('load', shell_homeLoaded);
|
||||
shell.isHomeLoaded = true;
|
||||
|
||||
if (Services.prefs.getBoolPref('b2g.orientation.animate')) {
|
||||
Cu.import('resource://gre/modules/OrientationChangeHandler.jsm');
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
libcutils.property_set('sys.boot_completed', '1');
|
||||
#endif
|
||||
|
||||
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
|
||||
|
||||
SystemAppProxy.setIsReady();
|
||||
if ('pendingChromeEvents' in shell) {
|
||||
shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell));
|
||||
}
|
||||
delete shell.pendingChromeEvents;
|
||||
});
|
||||
|
||||
shell.handleCmdLine();
|
||||
this.handleCmdLine();
|
||||
},
|
||||
|
||||
handleCmdLine: function shell_handleCmdLine() {
|
||||
@@ -710,6 +708,38 @@ var shell = {
|
||||
}
|
||||
#endif
|
||||
},
|
||||
|
||||
// This gets called when window.onload fires on the System app content window,
|
||||
// which means things in <html> are parsed and statically referenced <script>s
|
||||
// and <script defer>s are loaded and run.
|
||||
notifyContentWindowLoaded: function shell_notifyContentWindowLoaded() {
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
libcutils.property_set('sys.boot_completed', '1');
|
||||
#endif
|
||||
|
||||
// This will cause Gonk Widget to remove boot animation from the screen
|
||||
// and reveals the page.
|
||||
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
|
||||
|
||||
SystemAppProxy.setIsLoaded();
|
||||
},
|
||||
|
||||
// This gets called when the content sends us system-message-listener-ready
|
||||
// mozContentEvent, OR when an observer message tell us we should consider
|
||||
// the content as ready.
|
||||
notifyEventListenerReady: function shell_notifyEventListenerReady() {
|
||||
if (this._isEventListenerReady) {
|
||||
Cu.reportError('shell.js: SystemApp has already been declared as being ready.');
|
||||
return;
|
||||
}
|
||||
this._isEventListenerReady = true;
|
||||
|
||||
if (Services.prefs.getBoolPref('b2g.orientation.animate')) {
|
||||
Cu.import('resource://gre/modules/OrientationChangeHandler.jsm');
|
||||
}
|
||||
|
||||
SystemAppProxy.setIsReady();
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) {
|
||||
@@ -717,11 +747,13 @@ Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data)
|
||||
fullscreenorigin: data });
|
||||
}, "fullscreen-origin-change", false);
|
||||
|
||||
DOMApplicationRegistry.registryStarted.then(function () {
|
||||
shell.sendChromeEvent({ type: 'webapps-registry-start' });
|
||||
});
|
||||
DOMApplicationRegistry.registryReady.then(function () {
|
||||
shell.sendChromeEvent({ type: 'webapps-registry-ready' });
|
||||
// This event should be sent before System app returns with
|
||||
// system-message-listener-ready mozContentEvent, because it's on
|
||||
// the critical launch path of the app.
|
||||
SystemAppProxy._sendCustomEvent('mozChromeEvent', {
|
||||
type: 'webapps-registry-ready'
|
||||
}, /* noPending */ true);
|
||||
});
|
||||
|
||||
Services.obs.addObserver(function onBluetoothVolumeChange(subject, topic, data) {
|
||||
@@ -735,6 +767,18 @@ Services.obs.addObserver(function(subject, topic, data) {
|
||||
shell.sendCustomEvent('mozmemorypressure');
|
||||
}, 'memory-pressure', false);
|
||||
|
||||
Services.obs.addObserver(function(subject, topic, data) {
|
||||
shell.notifyEventListenerReady();
|
||||
}, 'system-message-listener-ready', false);
|
||||
|
||||
var permissionMap = new Map([
|
||||
['unknown', Services.perms.UNKNOWN_ACTION],
|
||||
['allow', Services.perms.ALLOW_ACTION],
|
||||
['deny', Services.perms.DENY_ACTION],
|
||||
['prompt', Services.perms.PROMPT_ACTION],
|
||||
]);
|
||||
var permissionMapRev = new Map(Array.from(permissionMap.entries()).reverse());
|
||||
|
||||
var CustomEventManager = {
|
||||
init: function custevt_init() {
|
||||
window.addEventListener("ContentStart", (function(evt) {
|
||||
@@ -780,11 +824,27 @@ var CustomEventManager = {
|
||||
Services.obs.notifyObservers({ wrappedJSObject: shell.contentBrowser },
|
||||
'ask-children-to-execute-copypaste-command', detail.cmd);
|
||||
break;
|
||||
case 'add-permission':
|
||||
Services.perms.add(Services.io.newURI(detail.uri, null, null),
|
||||
detail.permissionType, permissionMap.get(detail.permission));
|
||||
break;
|
||||
case 'remove-permission':
|
||||
Services.perms.remove(Services.io.newURI(detail.uri, null, null),
|
||||
detail.permissionType);
|
||||
break;
|
||||
case 'test-permission':
|
||||
let result = Services.perms.testExactPermission(
|
||||
Services.io.newURI(detail.uri, null, null), detail.permissionType);
|
||||
// Not equal check here because we want to prevent default only if it's not set
|
||||
if (result !== permissionMapRev.get(detail.permission)) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let DoCommandHelper = {
|
||||
var DoCommandHelper = {
|
||||
_event: null,
|
||||
setEvent: function docommand_setEvent(evt) {
|
||||
this._event = evt;
|
||||
@@ -885,7 +945,7 @@ var WebappsHelper = {
|
||||
}
|
||||
}
|
||||
|
||||
let KeyboardHelper = {
|
||||
var KeyboardHelper = {
|
||||
handleEvent: function keyboard_handleEvent(detail) {
|
||||
switch (detail.type) {
|
||||
case 'inputmethod-update-layouts':
|
||||
@@ -901,7 +961,7 @@ let KeyboardHelper = {
|
||||
}
|
||||
};
|
||||
|
||||
let SystemAppMozBrowserHelper = {
|
||||
var SystemAppMozBrowserHelper = {
|
||||
handleEvent: function systemAppMozBrowser_handleEvent(detail) {
|
||||
let request;
|
||||
let name;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="shellRemote"
|
||||
windowtype="navigator:remote-browser"
|
||||
sizemode="fullscreen"
|
||||
>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="shell.css" type="text/css">
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://b2g/content/shell_remote.js"> </script>
|
||||
</head>
|
||||
<body id="container">
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,71 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function debug(aStr) {
|
||||
// dump(" -*- ShellRemote.js: " + aStr + "\n");
|
||||
}
|
||||
|
||||
let remoteShell = {
|
||||
|
||||
get homeURL() {
|
||||
let systemAppManifestURL = Services.io.newURI(this.systemAppManifestURL, null, null);
|
||||
let shellRemoteURL = Services.prefs.getCharPref("b2g.multiscreen.system_remote_url");
|
||||
shellRemoteURL = Services.io.newURI(shellRemoteURL, null, systemAppManifestURL);
|
||||
return shellRemoteURL.spec;
|
||||
},
|
||||
|
||||
get systemAppManifestURL() {
|
||||
return Services.prefs.getCharPref("b2g.system_manifest_url");
|
||||
},
|
||||
|
||||
_started: false,
|
||||
|
||||
hasStarted: function () {
|
||||
return this._started;
|
||||
},
|
||||
|
||||
start: function () {
|
||||
this._started = true;
|
||||
|
||||
let homeURL = this.homeURL;
|
||||
if (!homeURL) {
|
||||
debug("ERROR! Remote home URL undefined.");
|
||||
return;
|
||||
}
|
||||
let manifestURL = this.systemAppManifestURL;
|
||||
// <html:iframe id="remote-systemapp"
|
||||
// mozbrowser="true" allowfullscreen="true"
|
||||
// src="blank.html"/>
|
||||
let systemAppFrame =
|
||||
document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
|
||||
systemAppFrame.setAttribute("id", "remote-systemapp");
|
||||
systemAppFrame.setAttribute("mozbrowser", "true");
|
||||
systemAppFrame.setAttribute("mozapp", manifestURL);
|
||||
systemAppFrame.setAttribute("allowfullscreen", "true");
|
||||
systemAppFrame.setAttribute("src", "blank.html");
|
||||
|
||||
let container = document.getElementById("container");
|
||||
this.contentBrowser = container.appendChild(systemAppFrame);
|
||||
this.contentBrowser.src = homeURL + window.location.hash;
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
window.onload = function() {
|
||||
if (remoteShell.hasStarted() == false) {
|
||||
remoteShell.start();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ chrome.jar:
|
||||
* content/shell.html (content/shell.html)
|
||||
* content/shell.js (content/shell.js)
|
||||
content/shell.css (content/shell.css)
|
||||
content/shell_remote.html (content/shell_remote.html)
|
||||
content/shell_remote.js (content/shell_remote.js)
|
||||
content/blank.html (content/blank.html)
|
||||
content/blank.css (content/blank.css)
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
|
||||
@@ -63,7 +63,7 @@ const kMessages = [
|
||||
kMessageAlertNotificationClose
|
||||
];
|
||||
|
||||
let AlertsHelper = {
|
||||
var AlertsHelper = {
|
||||
|
||||
_listeners: {},
|
||||
|
||||
|
||||
@@ -128,3 +128,7 @@ contract @mozilla.org/presentation-device/prompt;1 {4a300c26-e99b-4018-ab9b-c48c
|
||||
# PresentationRequestUIGlue.js
|
||||
component {ccc8a839-0b64-422b-8a60-fb2af0e376d0} PresentationRequestUIGlue.js
|
||||
contract @mozilla.org/presentation/requestuiglue;1 {ccc8a839-0b64-422b-8a60-fb2af0e376d0}
|
||||
|
||||
# KillSwitch.js
|
||||
component {b6eae5c6-971c-4772-89e5-5df626bf3f09} KillSwitch.js
|
||||
contract @mozilla.org/moz-kill-switch;1 {b6eae5c6-971c-4772-89e5-5df626bf3f09}
|
||||
|
||||
@@ -132,7 +132,7 @@ SSLExceptions.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
let ErrorPage = {
|
||||
var ErrorPage = {
|
||||
_addCertException: function(aMessage) {
|
||||
let frameLoaderOwner = aMessage.target.QueryInterface(Ci.nsIFrameLoaderOwner);
|
||||
let win = frameLoaderOwner.ownerDocument.defaultView;
|
||||
|
||||
@@ -117,7 +117,7 @@ const Observer = {
|
||||
|
||||
};
|
||||
|
||||
let Frames = this.Frames = {
|
||||
var Frames = this.Frames = {
|
||||
|
||||
list: () => SystemAppProxy.getFrames(),
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
function debug(s) {
|
||||
dump("-*- KillSwitch.js: " + s + "\n");
|
||||
}
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
||||
"@mozilla.org/childprocessmessagemanager;1",
|
||||
"nsIMessageSender");
|
||||
|
||||
const KILLSWITCH_CID = "{b6eae5c6-971c-4772-89e5-5df626bf3f09}";
|
||||
const KILLSWITCH_CONTRACTID = "@mozilla.org/moz-kill-switch;1";
|
||||
|
||||
const kEnableKillSwitch = "KillSwitch:Enable";
|
||||
const kEnableKillSwitchOK = "KillSwitch:Enable:OK";
|
||||
const kEnableKillSwitchKO = "KillSwitch:Enable:KO";
|
||||
|
||||
const kDisableKillSwitch = "KillSwitch:Disable";
|
||||
const kDisableKillSwitchOK = "KillSwitch:Disable:OK";
|
||||
const kDisableKillSwitchKO = "KillSwitch:Disable:KO";
|
||||
|
||||
function KillSwitch() {
|
||||
this._window = null;
|
||||
}
|
||||
|
||||
KillSwitch.prototype = {
|
||||
|
||||
__proto__: DOMRequestIpcHelper.prototype,
|
||||
|
||||
init: function(aWindow) {
|
||||
DEBUG && debug("init");
|
||||
this._window = aWindow;
|
||||
this.initDOMRequestHelper(this._window);
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
DEBUG && debug("KillSwitch: enable");
|
||||
|
||||
cpmm.addMessageListener(kEnableKillSwitchOK, this);
|
||||
cpmm.addMessageListener(kEnableKillSwitchKO, this);
|
||||
return this.createPromise((aResolve, aReject) => {
|
||||
cpmm.sendAsyncMessage(kEnableKillSwitch, {
|
||||
requestID: this.getPromiseResolverId({
|
||||
resolve: aResolve,
|
||||
reject: aReject
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
DEBUG && debug("KillSwitch: disable");
|
||||
|
||||
cpmm.addMessageListener(kDisableKillSwitchOK, this);
|
||||
cpmm.addMessageListener(kDisableKillSwitchKO, this);
|
||||
return this.createPromise((aResolve, aReject) => {
|
||||
cpmm.sendAsyncMessage(kDisableKillSwitch, {
|
||||
requestID: this.getPromiseResolverId({
|
||||
resolve: aResolve,
|
||||
reject: aReject
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
receiveMessage: function(message) {
|
||||
DEBUG && debug("Received: " + message.name);
|
||||
|
||||
cpmm.removeMessageListener(kEnableKillSwitchOK, this);
|
||||
cpmm.removeMessageListener(kEnableKillSwitchKO, this);
|
||||
cpmm.removeMessageListener(kDisableKillSwitchOK, this);
|
||||
cpmm.removeMessageListener(kDisableKillSwitchKO, this);
|
||||
|
||||
let req = this.takePromiseResolver(message.data.requestID);
|
||||
|
||||
switch (message.name) {
|
||||
case kEnableKillSwitchKO:
|
||||
case kDisableKillSwitchKO:
|
||||
req.reject(false);
|
||||
break;
|
||||
|
||||
case kEnableKillSwitchOK:
|
||||
case kDisableKillSwitchOK:
|
||||
req.resolve(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG && debug("Unrecognized message: " + message.name);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
classID : Components.ID(KILLSWITCH_CID),
|
||||
contractID : KILLSWITCH_CONTRACTID,
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIKillSwitch,
|
||||
Ci.nsIDOMGlobalPropertyInitializer,
|
||||
Ci.nsIObserver,
|
||||
Ci.nsIMessageListener,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([KillSwitch]);
|
||||
@@ -0,0 +1,505 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "KillSwitchMain" ];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "settings",
|
||||
"@mozilla.org/settingsService;1",
|
||||
"nsISettingsService");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
||||
"@mozilla.org/parentprocessmessagemanager;1",
|
||||
"nsIMessageBroadcaster");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "permMgr", function() {
|
||||
return Cc["@mozilla.org/permissionmanager;1"]
|
||||
.getService(Ci.nsIPermissionManager);
|
||||
});
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
|
||||
Cu.import("resource://gre/modules/systemlibs.js");
|
||||
return libcutils;
|
||||
});
|
||||
#else
|
||||
this.libcutils = null;
|
||||
#endif
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
const kEnableKillSwitch = "KillSwitch:Enable";
|
||||
const kEnableKillSwitchOK = "KillSwitch:Enable:OK";
|
||||
const kEnableKillSwitchKO = "KillSwitch:Enable:KO";
|
||||
|
||||
const kDisableKillSwitch = "KillSwitch:Disable";
|
||||
const kDisableKillSwitchOK = "KillSwitch:Disable:OK";
|
||||
const kDisableKillSwitchKO = "KillSwitch:Disable:KO";
|
||||
|
||||
const kMessages = [kEnableKillSwitch, kDisableKillSwitch];
|
||||
|
||||
const kXpcomShutdownObserverTopic = "xpcom-shutdown";
|
||||
|
||||
const kProperty = "persist.moz.killswitch";
|
||||
|
||||
const kUserValues =
|
||||
OS.Path.join(OS.Constants.Path.profileDir, "killswitch.json");
|
||||
|
||||
var inParent = Cc["@mozilla.org/xre/app-info;1"]
|
||||
.getService(Ci.nsIXULRuntime)
|
||||
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
||||
|
||||
function debug(aStr) {
|
||||
dump("--*-- KillSwitchMain: " + aStr + "\n");
|
||||
}
|
||||
|
||||
this.KillSwitchMain = {
|
||||
_ksState: null,
|
||||
_libcutils: null,
|
||||
|
||||
_enabledValues: {
|
||||
// List of settings to set to a specific value
|
||||
settings: {
|
||||
"debugger.remote-mode": "disabled",
|
||||
"developer.menu.enabled": false,
|
||||
"devtools.unrestricted": false,
|
||||
"lockscreen.enabled": true,
|
||||
"lockscreen.locked": true,
|
||||
"lockscreen.lock-immediately": true,
|
||||
"tethering.usb.enabled": false,
|
||||
"tethering.wifi.enabled": false,
|
||||
"ums.enabled": false
|
||||
},
|
||||
|
||||
// List of preferences to set to a specific value
|
||||
prefs: {
|
||||
"b2g.killswitch.test": true
|
||||
},
|
||||
|
||||
// List of Android properties to set to a specific value
|
||||
properties: {
|
||||
"persist.sys.usb.config": "none" // will change sys.usb.config and sys.usb.state
|
||||
},
|
||||
|
||||
// List of Android services to control
|
||||
services: {
|
||||
"adbd": "stop"
|
||||
}
|
||||
},
|
||||
|
||||
init: function() {
|
||||
DEBUG && debug("init");
|
||||
if (libcutils) {
|
||||
this._libcutils = libcutils;
|
||||
}
|
||||
|
||||
kMessages.forEach(m => {
|
||||
ppmm.addMessageListener(m, this);
|
||||
});
|
||||
|
||||
Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
|
||||
|
||||
this.readStateProperty();
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
kMessages.forEach(m => {
|
||||
ppmm.removeMessageListener(m, this);
|
||||
});
|
||||
|
||||
Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
|
||||
},
|
||||
|
||||
checkLibcUtils: function() {
|
||||
DEBUG && debug("checkLibcUtils");
|
||||
if (!this._libcutils) {
|
||||
debug("No proper libcutils binding, aborting.");
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
readStateProperty: function() {
|
||||
DEBUG && debug("readStateProperty");
|
||||
try {
|
||||
this.checkLibcUtils();
|
||||
} catch (ex) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._ksState =
|
||||
this._libcutils.property_get(kProperty, "false") === "true";
|
||||
},
|
||||
|
||||
writeStateProperty: function() {
|
||||
DEBUG && debug("writeStateProperty");
|
||||
try {
|
||||
this.checkLibcUtils();
|
||||
} catch (ex) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._libcutils.property_set(kProperty, this._ksState.toString());
|
||||
},
|
||||
|
||||
getPref(name, value) {
|
||||
let rv = undefined;
|
||||
|
||||
try {
|
||||
switch (typeof value) {
|
||||
case "boolean":
|
||||
rv = Services.prefs.getBoolPref(name, value);
|
||||
break;
|
||||
|
||||
case "number":
|
||||
rv = Services.prefs.getIntPref(name, value);
|
||||
break;
|
||||
|
||||
case "string":
|
||||
rv = Services.prefs.getCharPref(name, value);
|
||||
break;
|
||||
|
||||
default:
|
||||
debug("Unexpected pref type " + value);
|
||||
break;
|
||||
}
|
||||
} catch (ex) {
|
||||
}
|
||||
|
||||
return rv;
|
||||
},
|
||||
|
||||
setPref(name, value) {
|
||||
switch (typeof value) {
|
||||
case "boolean":
|
||||
Services.prefs.setBoolPref(name, value);
|
||||
break;
|
||||
|
||||
case "number":
|
||||
Services.prefs.setIntPref(name, value);
|
||||
break;
|
||||
|
||||
case "string":
|
||||
Services.prefs.setCharPref(name, value);
|
||||
break;
|
||||
|
||||
default:
|
||||
debug("Unexpected pref type " + value);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
doEnable: function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Make sure that the API cannot do a new |enable()| call once the
|
||||
// feature has been enabled, otherwise we will overwrite the user values.
|
||||
if (this._ksState) {
|
||||
reject(true);
|
||||
return;
|
||||
}
|
||||
|
||||
this.saveUserValues().then(() => {
|
||||
DEBUG && debug("Toggling settings: " +
|
||||
JSON.stringify(this._enabledValues.settings));
|
||||
|
||||
let lock = settings.createLock();
|
||||
for (let key of Object.keys(this._enabledValues.settings)) {
|
||||
lock.set(key, this._enabledValues.settings[key], this);
|
||||
}
|
||||
|
||||
DEBUG && debug("Toggling prefs: " +
|
||||
JSON.stringify(this._enabledValues.prefs));
|
||||
|
||||
for (let key of Object.keys(this._enabledValues.prefs)) {
|
||||
this.setPref(key, this._enabledValues.prefs[key]);
|
||||
}
|
||||
|
||||
DEBUG && debug("Toggling properties: " +
|
||||
JSON.stringify(this._enabledValues.properties));
|
||||
|
||||
for (let key of Object.keys(this._enabledValues.properties)) {
|
||||
this._libcutils.property_set(key, this._enabledValues.properties[key]);
|
||||
}
|
||||
|
||||
DEBUG && debug("Toggling services: " +
|
||||
JSON.stringify(this._enabledValues.services));
|
||||
|
||||
for (let key of Object.keys(this._enabledValues.services)) {
|
||||
let value = this._enabledValues.services[key];
|
||||
if (value !== "start" && value !== "stop") {
|
||||
debug("Unexpected service " + key + " value:" + value);
|
||||
}
|
||||
|
||||
this._libcutils.property_set("ctl." + value, key);
|
||||
}
|
||||
|
||||
this._ksState = true;
|
||||
this.writeStateProperty();
|
||||
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
DEBUG && debug("doEnable: " + err);
|
||||
|
||||
reject(false);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
saveUserValues: function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.checkLibcUtils();
|
||||
} catch (ex) {
|
||||
reject("nolibcutils");
|
||||
}
|
||||
|
||||
let _userValues = {
|
||||
settings: { },
|
||||
prefs: { },
|
||||
properties: { }
|
||||
};
|
||||
|
||||
// Those will be sync calls
|
||||
for (let key of Object.keys(this._enabledValues.prefs)) {
|
||||
_userValues.prefs[key] =
|
||||
this.getPref(key, this._enabledValues.prefs[key]);
|
||||
}
|
||||
|
||||
for (let key of Object.keys(this._enabledValues.properties)) {
|
||||
_userValues.properties[key] = this._libcutils.property_get(key);
|
||||
}
|
||||
|
||||
let self = this;
|
||||
let getCallback = {
|
||||
handleAbort: function(m) {
|
||||
DEBUG && debug("getCallback: handleAbort: m=" + m);
|
||||
reject(m);
|
||||
},
|
||||
|
||||
handleError: function(m) {
|
||||
DEBUG && debug("getCallback: handleError: m=" + m);
|
||||
reject(m);
|
||||
},
|
||||
|
||||
handle: function(n, v) {
|
||||
DEBUG && debug("getCallback: handle: n=" + n + " ; v=" + v);
|
||||
|
||||
if (self._pendingSettingsGet) {
|
||||
// We have received a settings callback value for saving user data
|
||||
let pending = self._pendingSettingsGet.indexOf(n);
|
||||
if (pending !== -1) {
|
||||
_userValues.settings[n] = v;
|
||||
self._pendingSettingsGet.splice(pending, 1);
|
||||
}
|
||||
|
||||
if (self._pendingSettingsGet.length === 0) {
|
||||
delete self._pendingSettingsGet;
|
||||
let payload = JSON.stringify(_userValues);
|
||||
DEBUG && debug("Dumping to " + kUserValues + ": " + payload);
|
||||
OS.File.writeAtomic(kUserValues, payload).then(
|
||||
function writeOk() {
|
||||
resolve(true);
|
||||
},
|
||||
function writeNok(err) {
|
||||
reject("write error");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// For settings we have to wait all the callbacks to come back before
|
||||
// we can resolve or reject
|
||||
this._pendingSettingsGet = [];
|
||||
let lock = settings.createLock();
|
||||
for (let key of Object.keys(this._enabledValues.settings)) {
|
||||
this._pendingSettingsGet.push(key);
|
||||
lock.get(key, getCallback);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
doDisable: function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.restoreUserValues().then(() => {
|
||||
this._ksState = false;
|
||||
this.writeStateProperty();
|
||||
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
DEBUG && debug("doDisable: " + err);
|
||||
|
||||
reject(false);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
restoreUserValues: function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.checkLibcUtils();
|
||||
} catch (ex) {
|
||||
reject("nolibcutils");
|
||||
}
|
||||
|
||||
OS.File.read(kUserValues, { encoding: "utf-8" }).then(content => {
|
||||
let values = JSON.parse(content);
|
||||
|
||||
for (let key of Object.keys(values.prefs)) {
|
||||
this.setPref(key, values.prefs[key]);
|
||||
}
|
||||
|
||||
for (let key of Object.keys(values.properties)) {
|
||||
this._libcutils.property_set(key, values.properties[key]);
|
||||
}
|
||||
|
||||
let self = this;
|
||||
let saveCallback = {
|
||||
handleAbort: function(m) {
|
||||
DEBUG && debug("saveCallback: handleAbort: m=" + m);
|
||||
reject(m);
|
||||
},
|
||||
|
||||
handleError: function(m) {
|
||||
DEBUG && debug("saveCallback: handleError: m=" + m);
|
||||
reject(m);
|
||||
},
|
||||
|
||||
handle: function(n, v) {
|
||||
DEBUG && debug("saveCallback: handle: n=" + n + " ; v=" + v);
|
||||
|
||||
if (self._pendingSettingsSet) {
|
||||
// We have received a settings callback value for setting user data
|
||||
let pending = self._pendingSettingsSet.indexOf(n);
|
||||
if (pending !== -1) {
|
||||
self._pendingSettingsSet.splice(pending, 1);
|
||||
}
|
||||
|
||||
if (self._pendingSettingsSet.length === 0) {
|
||||
delete self._pendingSettingsSet;
|
||||
DEBUG && debug("Restored from " + kUserValues + ": " + JSON.stringify(values));
|
||||
resolve(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// For settings we have to wait all the callbacks to come back before
|
||||
// we can resolve or reject
|
||||
this._pendingSettingsSet = [];
|
||||
let lock = settings.createLock();
|
||||
for (let key of Object.keys(values.settings)) {
|
||||
this._pendingSettingsSet.push(key);
|
||||
lock.set(key, values.settings[key], saveCallback);
|
||||
}
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Settings Callbacks
|
||||
handle: function(aName, aValue) {
|
||||
DEBUG && debug("handle: aName=" + aName + " ; aValue=" + aValue);
|
||||
// We don't have to do anything for now.
|
||||
},
|
||||
|
||||
handleAbort: function(aMessage) {
|
||||
debug("handleAbort: " + JSON.stringify(aMessage));
|
||||
throw Cr.NS_ERROR_ABORT;
|
||||
},
|
||||
|
||||
handleError: function(aMessage) {
|
||||
debug("handleError: " + JSON.stringify(aMessage));
|
||||
throw Cr.NS_ERROR_FAILURE;
|
||||
},
|
||||
|
||||
// addObserver
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case kXpcomShutdownObserverTopic:
|
||||
this.uninit();
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG && debug("Wrong observer topic: " + aTopic);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// addMessageListener
|
||||
receiveMessage: function(aMessage) {
|
||||
let hasPermission = aMessage.target.assertPermission("killswitch");
|
||||
DEBUG && debug("hasPermission: " + hasPermission);
|
||||
|
||||
if (!hasPermission) {
|
||||
debug("Message " + aMessage.name + " from a process with no killswitch perm.");
|
||||
aMessage.target.killChild();
|
||||
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
||||
return;
|
||||
}
|
||||
|
||||
function returnMessage(name, data) {
|
||||
if (aMessage.target) {
|
||||
data.requestID = aMessage.data.requestID;
|
||||
try {
|
||||
aMessage.target.sendAsyncMessage(name, data);
|
||||
} catch (e) {
|
||||
if (DEBUG) debug("Return message failed, " + name + ": " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (aMessage.name) {
|
||||
case kEnableKillSwitch:
|
||||
this.doEnable().then(
|
||||
() => {
|
||||
returnMessage(kEnableKillSwitchOK, {});
|
||||
},
|
||||
err => {
|
||||
debug("doEnable failed: " + err);
|
||||
returnMessage(kEnableKillSwitchKO, {});
|
||||
}
|
||||
);
|
||||
break;
|
||||
case kDisableKillSwitch:
|
||||
this.doDisable().then(
|
||||
() => {
|
||||
returnMessage(kDisableKillSwitchOK, {});
|
||||
},
|
||||
err => {
|
||||
debug("doDisable failed: " + err);
|
||||
returnMessage(kDisableKillSwitchKO, {});
|
||||
}
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
debug("Unsupported message: " + aMessage.name);
|
||||
aMessage.target && aMessage.target.killChild();
|
||||
throw Cr.NS_ERROR_ILLEGAL_VALUE;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This code should ALWAYS be living only on the parent side.
|
||||
if (!inParent) {
|
||||
debug("KillSwitchMain should only be living on parent side.");
|
||||
throw Cr.NS_ERROR_ABORT;
|
||||
} else {
|
||||
this.KillSwitchMain.init();
|
||||
}
|
||||
@@ -24,7 +24,7 @@ function debug(msg) {
|
||||
dump("LogCapture.jsm: " + msg + "\n");
|
||||
}
|
||||
|
||||
let LogCapture = {
|
||||
var LogCapture = {
|
||||
ensureLoaded: function() {
|
||||
if (!this.ctypes) {
|
||||
this.load();
|
||||
@@ -194,17 +194,26 @@ let LogCapture = {
|
||||
* as an ArrayBuffer.
|
||||
*/
|
||||
getScreenshot: function() {
|
||||
this.ensureLoaded();
|
||||
let deferred = Promise.defer();
|
||||
try {
|
||||
this.ensureLoaded();
|
||||
|
||||
let fr = Cc["@mozilla.org/files/filereader;1"]
|
||||
.createInstance(Ci.nsIDOMFileReader);
|
||||
let fr = Cc["@mozilla.org/files/filereader;1"]
|
||||
.createInstance(Ci.nsIDOMFileReader);
|
||||
|
||||
fr.onload = function(evt) {
|
||||
deferred.resolve(new Uint8Array(evt.target.result));
|
||||
};
|
||||
fr.onload = function(evt) {
|
||||
deferred.resolve(new Uint8Array(evt.target.result));
|
||||
};
|
||||
|
||||
fr.readAsArrayBuffer(Screenshot.get());
|
||||
fr.onerror = function(evt) {
|
||||
deferred.reject(evt);
|
||||
};
|
||||
|
||||
fr.readAsArrayBuffer(Screenshot.get());
|
||||
} catch(e) {
|
||||
// We pass any errors through to the deferred Promise
|
||||
deferred.reject(e);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,14 @@ function parseLogArray(array) {
|
||||
let data = new DataView(array.buffer);
|
||||
let byteString = String.fromCharCode.apply(null, array);
|
||||
|
||||
// Length of bytes that precede the payload of a log message
|
||||
// From the 5 Uint32 and 1 Uint8 reads
|
||||
const HEADER_LENGTH = 21;
|
||||
|
||||
let logMessages = [];
|
||||
let pos = 0;
|
||||
|
||||
while (pos < byteString.length) {
|
||||
while (pos + HEADER_LENGTH < byteString.length) {
|
||||
// Parse a single log entry
|
||||
|
||||
// Track current offset from global position
|
||||
@@ -203,33 +207,43 @@ function formatLogMessage(logMessage) {
|
||||
": " + logMessage.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to a utf-8 Uint8Array
|
||||
* @param {String} str
|
||||
* @return {Uint8Array}
|
||||
*/
|
||||
function textEncode(str) {
|
||||
return new TextEncoder("utf-8").encode(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty-print an array of bytes read from a log file by parsing then
|
||||
* threadtime formatting its entries.
|
||||
* @param array {Uint8Array} Array of a log file's bytes
|
||||
* @return {String} Pretty-printed log
|
||||
* @return {Uint8Array} Pretty-printed log
|
||||
*/
|
||||
function prettyPrintLogArray(array) {
|
||||
let logMessages = parseLogArray(array);
|
||||
return logMessages.map(formatLogMessage).join("");
|
||||
return textEncode(logMessages.map(formatLogMessage).join(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty-print an array read from the list of propreties.
|
||||
* @param {Object} Object representing the properties
|
||||
* @return {String} Human-readable string of property name: property value
|
||||
* @return {Uint8Array} Human-readable string of property name: property value
|
||||
*/
|
||||
function prettyPrintPropertiesArray(properties) {
|
||||
let propertiesString = "";
|
||||
for(let propName in properties) {
|
||||
propertiesString += "[" + propName + "]: [" + properties[propName] + "]\n";
|
||||
}
|
||||
return propertiesString;
|
||||
return textEncode(propertiesString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty-print a normal array. Does nothing.
|
||||
* @param array {Uint8Array} Input array
|
||||
* @return {Uint8Array} The same array
|
||||
*/
|
||||
function prettyPrintArray(array) {
|
||||
return array;
|
||||
|
||||
@@ -7,15 +7,16 @@
|
||||
* response to a sufficiently large acceleration (a shake), it will save log
|
||||
* files to an arbitrary directory which it will then return on a
|
||||
* 'capture-logs-success' event with detail.logFilenames representing each log
|
||||
* file's filename in the directory. If an error occurs it will instead produce
|
||||
* a 'capture-logs-error' event.
|
||||
* file's name and detail.logPaths representing the patch to each log file or
|
||||
* the path to the archive.
|
||||
* If an error occurs it will instead produce a 'capture-logs-error' event.
|
||||
* We send a capture-logs-start events to notify the system app and the user,
|
||||
* since dumping can be a bit long sometimes.
|
||||
*/
|
||||
|
||||
/* enable Mozilla javascript extensions and global strictness declaration,
|
||||
* disable valid this checking */
|
||||
/* jshint moz: true */
|
||||
/* jshint moz: true, esnext: true */
|
||||
/* jshint -W097 */
|
||||
/* jshint -W040 */
|
||||
/* global Services, Components, dump, LogCapture, LogParser,
|
||||
@@ -23,11 +24,17 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
// Constants for creating zip file taken from toolkit/webapps/tests/head.js
|
||||
const PR_RDWR = 0x04;
|
||||
const PR_CREATE_FILE = 0x08;
|
||||
const PR_TRUNCATE = 0x20;
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LogCapture", "resource://gre/modules/LogCapture.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LogParser", "resource://gre/modules/LogParser.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
@@ -51,17 +58,30 @@ function debug(msg) {
|
||||
|
||||
/**
|
||||
* An empirically determined amount of acceleration corresponding to a
|
||||
* shake
|
||||
* shake.
|
||||
*/
|
||||
const EXCITEMENT_THRESHOLD = 500;
|
||||
/**
|
||||
* The maximum fraction to update the excitement value per frame. This
|
||||
* corresponds to requiring shaking for approximately 10 motion events (1.6
|
||||
* seconds)
|
||||
*/
|
||||
const EXCITEMENT_FILTER_ALPHA = 0.2;
|
||||
const DEVICE_MOTION_EVENT = "devicemotion";
|
||||
const SCREEN_CHANGE_EVENT = "screenchange";
|
||||
const CAPTURE_LOGS_CONTENT_EVENT = "requestSystemLogs";
|
||||
const CAPTURE_LOGS_START_EVENT = "capture-logs-start";
|
||||
const CAPTURE_LOGS_ERROR_EVENT = "capture-logs-error";
|
||||
const CAPTURE_LOGS_SUCCESS_EVENT = "capture-logs-success";
|
||||
|
||||
let LogShake = {
|
||||
var LogShake = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
|
||||
/**
|
||||
* If LogShake is in QA Mode, which bundles all files into a compressed archive
|
||||
*/
|
||||
qaModeEnabled: false,
|
||||
|
||||
/**
|
||||
* If LogShake is listening for device motion events. Required due to lag
|
||||
* between HAL layer of device motion events and listening for device motion
|
||||
@@ -69,12 +89,29 @@ let LogShake = {
|
||||
*/
|
||||
deviceMotionEnabled: false,
|
||||
|
||||
/**
|
||||
* We only listen to motion events when the screen is enabled, keep track
|
||||
* of its state.
|
||||
*/
|
||||
screenEnabled: true,
|
||||
|
||||
/**
|
||||
* Flag monitoring if the preference to enable shake to capture is
|
||||
* enabled in gaia.
|
||||
*/
|
||||
listenToDeviceMotion: true,
|
||||
|
||||
/**
|
||||
* If a capture has been requested and is waiting for reads/parsing. Used for
|
||||
* debouncing.
|
||||
*/
|
||||
captureRequested: false,
|
||||
|
||||
/**
|
||||
* The current excitement (movement) level
|
||||
*/
|
||||
excitement: 0,
|
||||
|
||||
/**
|
||||
* Map of files which have log-type information to their parsers
|
||||
*/
|
||||
@@ -109,6 +146,10 @@ let LogShake = {
|
||||
screenEnabled: true
|
||||
}});
|
||||
|
||||
// Reset excitement to clear residual motion
|
||||
this.excitement = 0;
|
||||
|
||||
SystemAppProxy.addEventListener(CAPTURE_LOGS_CONTENT_EVENT, this, false);
|
||||
SystemAppProxy.addEventListener(SCREEN_CHANGE_EVENT, this, false);
|
||||
|
||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||
@@ -129,6 +170,10 @@ let LogShake = {
|
||||
case SCREEN_CHANGE_EVENT:
|
||||
this.handleScreenChangeEvent(event);
|
||||
break;
|
||||
|
||||
case CAPTURE_LOGS_CONTENT_EVENT:
|
||||
this.startCapture();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -141,8 +186,30 @@ let LogShake = {
|
||||
}
|
||||
},
|
||||
|
||||
enableQAMode: function() {
|
||||
debug("Enabling QA Mode");
|
||||
this.qaModeEnabled = true;
|
||||
},
|
||||
|
||||
disableQAMode: function() {
|
||||
debug("Disabling QA Mode");
|
||||
this.qaModeEnabled = false;
|
||||
},
|
||||
|
||||
enableDeviceMotionListener: function() {
|
||||
this.listenToDeviceMotion = true;
|
||||
this.startDeviceMotionListener();
|
||||
},
|
||||
|
||||
disableDeviceMotionListener: function() {
|
||||
this.listenToDeviceMotion = false;
|
||||
this.stopDeviceMotionListener();
|
||||
},
|
||||
|
||||
startDeviceMotionListener: function() {
|
||||
if (!this.deviceMotionEnabled) {
|
||||
if (!this.deviceMotionEnabled &&
|
||||
this.listenToDeviceMotion &&
|
||||
this.screenEnabled) {
|
||||
SystemAppProxy.addEventListener(DEVICE_MOTION_EVENT, this, false);
|
||||
this.deviceMotionEnabled = true;
|
||||
}
|
||||
@@ -164,33 +231,41 @@ let LogShake = {
|
||||
return;
|
||||
}
|
||||
|
||||
var acc = event.accelerationIncludingGravity;
|
||||
let acc = event.accelerationIncludingGravity;
|
||||
|
||||
var excitement = acc.x * acc.x + acc.y * acc.y + acc.z * acc.z;
|
||||
// Updates excitement by a factor of at most alpha, ignoring sudden device
|
||||
// motion. See bug #1101994 for more information.
|
||||
let newExcitement = acc.x * acc.x + acc.y * acc.y + acc.z * acc.z;
|
||||
this.excitement += (newExcitement - this.excitement) * EXCITEMENT_FILTER_ALPHA;
|
||||
|
||||
if (excitement > EXCITEMENT_THRESHOLD) {
|
||||
if (!this.captureRequested) {
|
||||
this.captureRequested = true;
|
||||
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_START_EVENT, {});
|
||||
this.captureLogs().then(logResults => {
|
||||
// On resolution send the success event to the requester
|
||||
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_SUCCESS_EVENT, {
|
||||
logFilenames: logResults.logFilenames,
|
||||
logPrefix: logResults.logPrefix
|
||||
});
|
||||
this.captureRequested = false;
|
||||
},
|
||||
error => {
|
||||
// On an error send the error event
|
||||
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_ERROR_EVENT, {error: error});
|
||||
this.captureRequested = false;
|
||||
});
|
||||
}
|
||||
if (this.excitement > EXCITEMENT_THRESHOLD) {
|
||||
this.startCapture();
|
||||
}
|
||||
},
|
||||
|
||||
startCapture: function() {
|
||||
if (this.captureRequested) {
|
||||
return;
|
||||
}
|
||||
this.captureRequested = true;
|
||||
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_START_EVENT, {});
|
||||
this.captureLogs().then(logResults => {
|
||||
// On resolution send the success event to the requester
|
||||
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_SUCCESS_EVENT, {
|
||||
logPaths: logResults.logPaths,
|
||||
logFilenames: logResults.logFilenames
|
||||
});
|
||||
this.captureRequested = false;
|
||||
}, error => {
|
||||
// On an error send the error event
|
||||
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_ERROR_EVENT, {error: error});
|
||||
this.captureRequested = false;
|
||||
});
|
||||
},
|
||||
|
||||
handleScreenChangeEvent: function(event) {
|
||||
if (event.detail.screenEnabled) {
|
||||
this.screenEnabled = event.detail.screenEnabled;
|
||||
if (this.screenEnabled) {
|
||||
this.startDeviceMotionListener();
|
||||
} else {
|
||||
this.stopDeviceMotionListener();
|
||||
@@ -202,15 +277,18 @@ let LogShake = {
|
||||
* resolve to an array of log filenames.
|
||||
*/
|
||||
captureLogs: function() {
|
||||
let logArrays = this.readLogs();
|
||||
return saveLogs(logArrays);
|
||||
return this.readLogs().then(logArrays => {
|
||||
return this.saveLogs(logArrays);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Read in all log files, returning their formatted contents
|
||||
* @return {Promise<Array>}
|
||||
*/
|
||||
readLogs: function() {
|
||||
let logArrays = {};
|
||||
let readPromises = [];
|
||||
|
||||
try {
|
||||
logArrays["properties"] =
|
||||
@@ -220,14 +298,15 @@ let LogShake = {
|
||||
}
|
||||
|
||||
// Let Gecko perfom the dump to a file, and just collect it
|
||||
try {
|
||||
let readAboutMemoryPromise = new Promise(resolve => {
|
||||
// Wrap the readAboutMemory promise to make it infallible
|
||||
LogCapture.readAboutMemory().then(aboutMemory => {
|
||||
let file = OS.Path.basename(aboutMemory);
|
||||
let logArray;
|
||||
try {
|
||||
logArray = LogCapture.readLogFile(aboutMemory);
|
||||
if (!logArray) {
|
||||
debug("LogCapture.readLogFile() returned nothing about:memory ");
|
||||
debug("LogCapture.readLogFile() returned nothing for about:memory");
|
||||
}
|
||||
// We need to remove the dumped file, now that we have it in memory
|
||||
OS.File.remove(aboutMemory);
|
||||
@@ -235,18 +314,25 @@ let LogShake = {
|
||||
Cu.reportError("Unable to handle about:memory dump: " + ex);
|
||||
}
|
||||
logArrays[file] = LogParser.prettyPrintArray(logArray);
|
||||
resolve();
|
||||
}, ex => {
|
||||
Cu.reportError("Unable to get about:memory dump: " + ex);
|
||||
resolve();
|
||||
});
|
||||
} catch (ex) {
|
||||
Cu.reportError("Unable to get about:memory dump: " + ex);
|
||||
}
|
||||
});
|
||||
readPromises.push(readAboutMemoryPromise);
|
||||
|
||||
try {
|
||||
// Wrap the promise to make it infallible
|
||||
let readScreenshotPromise = new Promise(resolve => {
|
||||
LogCapture.getScreenshot().then(screenshot => {
|
||||
logArrays["logshake-screenshot.png"] = screenshot;
|
||||
logArrays["screenshot.png"] = screenshot;
|
||||
resolve();
|
||||
}, ex => {
|
||||
Cu.reportError("Unable to get screenshot dump: " + ex);
|
||||
resolve();
|
||||
});
|
||||
} catch (ex) {
|
||||
Cu.reportError("Unable to get screenshot dump: " + ex);
|
||||
}
|
||||
});
|
||||
readPromises.push(readScreenshotPromise);
|
||||
|
||||
for (let loc in this.LOGS_WITH_PARSERS) {
|
||||
let logArray;
|
||||
@@ -268,7 +354,31 @@ let LogShake = {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return logArrays;
|
||||
|
||||
// Because the promises we depend upon can't fail this means that the
|
||||
// blocking log reads will always be honored.
|
||||
return Promise.all(readPromises).then(() => {
|
||||
return logArrays;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the formatted arrays of log files to an sdcard if available
|
||||
*/
|
||||
saveLogs: function(logArrays) {
|
||||
if (!logArrays || Object.keys(logArrays).length === 0) {
|
||||
return Promise.reject("Zero logs saved");
|
||||
}
|
||||
|
||||
if (this.qaModeEnabled) {
|
||||
return makeBaseLogsDirectory().then(writeLogArchive(logArrays),
|
||||
rejectFunction("Error making base log directory"));
|
||||
} else {
|
||||
return makeBaseLogsDirectory().then(makeLogsDirectory,
|
||||
rejectFunction("Error making base log directory"))
|
||||
.then(writeLogFiles(logArrays),
|
||||
rejectFunction("Error creating log directory"));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -307,82 +417,168 @@ function getLogDirectoryRoot() {
|
||||
return "logs";
|
||||
}
|
||||
|
||||
function getLogDirectory() {
|
||||
function getLogIdentifier() {
|
||||
let d = new Date();
|
||||
d = new Date(d.getTime() - d.getTimezoneOffset() * 60000);
|
||||
let timestamp = d.toISOString().slice(0, -5).replace(/[:T]/g, "-");
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the formatted arrays of log files to an sdcard if available
|
||||
*/
|
||||
function saveLogs(logArrays) {
|
||||
if (!logArrays || Object.keys(logArrays).length === 0) {
|
||||
return Promise.resolve({
|
||||
logFilenames: [],
|
||||
logPrefix: ""
|
||||
});
|
||||
}
|
||||
function rejectFunction(message) {
|
||||
return function(err) {
|
||||
debug(message + ": " + err);
|
||||
return Promise.reject(err);
|
||||
};
|
||||
}
|
||||
|
||||
let sdcardPrefix, dirNameRoot, dirName;
|
||||
function makeBaseLogsDirectory() {
|
||||
let sdcardPrefix;
|
||||
try {
|
||||
sdcardPrefix = getSdcardPrefix();
|
||||
dirNameRoot = getLogDirectoryRoot();
|
||||
dirName = getLogDirectory();
|
||||
} catch(e) {
|
||||
// Return promise failed with exception e
|
||||
// Handles missing sdcard
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
debug("making a directory all the way from " + sdcardPrefix + " to " + (sdcardPrefix + "/" + dirNameRoot + "/" + dirName) );
|
||||
let dirNameRoot = getLogDirectoryRoot();
|
||||
|
||||
let logsRoot = OS.Path.join(sdcardPrefix, dirNameRoot);
|
||||
|
||||
debug("Creating base log directory at root " + sdcardPrefix);
|
||||
|
||||
return OS.File.makeDir(logsRoot, {from: sdcardPrefix}).then(
|
||||
function() {
|
||||
debug("First OS.File.makeDir done");
|
||||
let logsDir = OS.Path.join(logsRoot, dirName);
|
||||
debug("Creating " + logsDir);
|
||||
return OS.File.makeDir(logsDir, {ignoreExisting: false}).then(
|
||||
function() {
|
||||
debug("Created: " + logsDir);
|
||||
// Now the directory is guaranteed to exist, save the logs
|
||||
let logFilenames = [];
|
||||
let saveRequests = [];
|
||||
return {
|
||||
sdcardPrefix: sdcardPrefix,
|
||||
basePrefix: dirNameRoot
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
debug("Will now traverse logArrays: " + logArrays.length);
|
||||
function makeLogsDirectory({sdcardPrefix, basePrefix}) {
|
||||
let dirName = getLogIdentifier();
|
||||
|
||||
for (let logLocation in logArrays) {
|
||||
debug("requesting save of " + logLocation);
|
||||
let logArray = logArrays[logLocation];
|
||||
// The filename represents the relative path within the SD card, not the
|
||||
// absolute path because Gaia will refer to it using the DeviceStorage
|
||||
// API
|
||||
let filename = OS.Path.join(dirNameRoot, dirName, getLogFilename(logLocation));
|
||||
logFilenames.push(filename);
|
||||
let saveRequest = OS.File.writeAtomic(OS.Path.join(sdcardPrefix, filename), logArray);
|
||||
saveRequests.push(saveRequest);
|
||||
}
|
||||
let logsRoot = OS.Path.join(sdcardPrefix, basePrefix);
|
||||
let logsDir = OS.Path.join(logsRoot, dirName);
|
||||
|
||||
return Promise.all(saveRequests).then(
|
||||
function() {
|
||||
debug("returning logfilenames: "+logFilenames.toSource());
|
||||
return {
|
||||
logFilenames: logFilenames,
|
||||
logPrefix: OS.Path.join(dirNameRoot, dirName)
|
||||
};
|
||||
}, function(err) {
|
||||
debug("Error at some save request: " + err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}, function(err) {
|
||||
debug("Error at OS.File.makeDir for " + logsDir + ": " + err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}, function(err) {
|
||||
debug("Error at first OS.File.makeDir: " + err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
debug("Creating base log directory at root " + sdcardPrefix);
|
||||
debug("Final created directory will be " + logsDir);
|
||||
|
||||
return OS.File.makeDir(logsDir, {ignoreExisting: false}).then(
|
||||
function() {
|
||||
debug("Created: " + logsDir);
|
||||
return {
|
||||
logPrefix: OS.Path.join(basePrefix, dirName),
|
||||
sdcardPrefix: sdcardPrefix
|
||||
};
|
||||
},
|
||||
rejectFunction("Error at OS.File.makeDir for " + logsDir)
|
||||
);
|
||||
}
|
||||
|
||||
function getFile(filename) {
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.initWithPath(filename);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a zip file
|
||||
* @param {String} absoluteZipFilename - Fully qualified desired location of the zip file
|
||||
* @param {Map<String, Uint8Array>} logArrays - Map from log location to log data
|
||||
* @return {Array<String>} Paths of entries in the archive
|
||||
*/
|
||||
function makeZipFile(absoluteZipFilename, logArrays) {
|
||||
let logFilenames = [];
|
||||
let zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
|
||||
let zipFile = getFile(absoluteZipFilename);
|
||||
zipWriter.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
|
||||
|
||||
for (let logLocation in logArrays) {
|
||||
let logArray = logArrays[logLocation];
|
||||
let logFilename = getLogFilename(logLocation);
|
||||
logFilenames.push(logFilename);
|
||||
|
||||
debug("Adding " + logFilename + " to the zip");
|
||||
let logArrayStream = Cc["@mozilla.org/io/arraybuffer-input-stream;1"]
|
||||
.createInstance(Ci.nsIArrayBufferInputStream);
|
||||
// Set data to be copied, default offset to 0 because it is not present on
|
||||
// ArrayBuffer objects
|
||||
logArrayStream.setData(logArray.buffer, logArray.byteOffset || 0,
|
||||
logArray.byteLength);
|
||||
|
||||
zipWriter.addEntryStream(logFilename, Date.now(),
|
||||
Ci.nsIZipWriter.COMPRESSION_DEFAULT,
|
||||
logArrayStream, false);
|
||||
}
|
||||
zipWriter.close();
|
||||
|
||||
return logFilenames;
|
||||
}
|
||||
|
||||
function writeLogArchive(logArrays) {
|
||||
return function({sdcardPrefix, basePrefix}) {
|
||||
// Now the directory is guaranteed to exist, save the logs into their
|
||||
// archive file
|
||||
|
||||
let zipFilename = getLogIdentifier() + "-logs.zip";
|
||||
let zipPath = OS.Path.join(basePrefix, zipFilename);
|
||||
let zipPrefix = OS.Path.dirname(zipPath);
|
||||
let absoluteZipPath = OS.Path.join(sdcardPrefix, zipPath);
|
||||
|
||||
debug("Creating zip file at " + zipPath);
|
||||
let logFilenames = [];
|
||||
try {
|
||||
logFilenames = makeZipFile(absoluteZipPath, logArrays);
|
||||
} catch(e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
debug("Zip file created");
|
||||
|
||||
return {
|
||||
logFilenames: logFilenames,
|
||||
logPaths: [zipPath],
|
||||
compressed: true
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function writeLogFiles(logArrays) {
|
||||
return function({sdcardPrefix, logPrefix}) {
|
||||
// Now the directory is guaranteed to exist, save the logs
|
||||
let logFilenames = [];
|
||||
let logPaths = [];
|
||||
let saveRequests = [];
|
||||
|
||||
for (let logLocation in logArrays) {
|
||||
debug("Requesting save of " + logLocation);
|
||||
let logArray = logArrays[logLocation];
|
||||
let logFilename = getLogFilename(logLocation);
|
||||
// The local pathrepresents the relative path within the SD card, not the
|
||||
// absolute path because Gaia will refer to it using the DeviceStorage
|
||||
// API
|
||||
let localPath = OS.Path.join(logPrefix, logFilename);
|
||||
|
||||
logFilenames.push(logFilename);
|
||||
logPaths.push(localPath);
|
||||
|
||||
let absolutePath = OS.Path.join(sdcardPrefix, localPath);
|
||||
let saveRequest = OS.File.writeAtomic(absolutePath, logArray);
|
||||
saveRequests.push(saveRequest);
|
||||
}
|
||||
|
||||
return Promise.all(saveRequests).then(
|
||||
function() {
|
||||
return {
|
||||
logFilenames: logFilenames,
|
||||
logPaths: logPaths,
|
||||
compressed: false
|
||||
};
|
||||
},
|
||||
rejectFunction("Error at some save request")
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
LogShake.init();
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["MultiscreenHandler"];
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function debug(aStr) {
|
||||
// dump("MultiscreenHandler: " + aStr + "\n");
|
||||
}
|
||||
|
||||
let window = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
// Multi-screen support on b2g. The following implementation will open a new
|
||||
// top-level window once we receive a display connected event.
|
||||
let MultiscreenHandler = {
|
||||
|
||||
topLevelWindows: new Map(),
|
||||
|
||||
init: function init() {
|
||||
Services.obs.addObserver(this, "display-changed", false);
|
||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
Services.obs.removeObserver(this, "display-changed");
|
||||
Services.obs.removeObserver(this, "xpcom-shutdown");
|
||||
},
|
||||
|
||||
observe: function observe(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "display-changed":
|
||||
this.handleDisplayChangeEvent(aSubject);
|
||||
break
|
||||
case "xpcom-shutdown":
|
||||
this.uninit();
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
openTopLevelWindow: function openTopLevelWindow(aDisplay) {
|
||||
if (this.topLevelWindows.get(aDisplay.id)) {
|
||||
debug("Top level window for display id: " + aDisplay.id + " has been opened.");
|
||||
return;
|
||||
}
|
||||
|
||||
let flags = Services.prefs.getCharPref("toolkit.defaultChromeFeatures") +
|
||||
",mozDisplayId=" + aDisplay.id;
|
||||
let remoteShellURL = Services.prefs.getCharPref("b2g.multiscreen.chrome_remote_url") +
|
||||
"#" + aDisplay.id;
|
||||
let win = Services.ww.openWindow(null, remoteShellURL, "myTopWindow" + aDisplay.id, flags, null);
|
||||
|
||||
this.topLevelWindows.set(aDisplay.id, win);
|
||||
},
|
||||
|
||||
closeTopLevelWindow: function closeTopLevelWindow(aDisplay) {
|
||||
let win = this.topLevelWindows.get(aDisplay.id);
|
||||
|
||||
if (win) {
|
||||
win.close();
|
||||
this.topLevelWindows.delete(aDisplay.id);
|
||||
}
|
||||
},
|
||||
|
||||
handleDisplayChangeEvent: function handleDisplayChangeEvent(aSubject) {
|
||||
|
||||
let display = aSubject.QueryInterface(Ci.nsIDisplayInfo);
|
||||
let name = "multiscreen.enabled";
|
||||
let req = window.navigator.mozSettings.createLock().get(name);
|
||||
|
||||
req.addEventListener("success", () => {
|
||||
let isMultiscreenEnabled = req.result[name];
|
||||
if (display.connected) {
|
||||
if (isMultiscreenEnabled) {
|
||||
this.openTopLevelWindow(display);
|
||||
}
|
||||
} else {
|
||||
this.closeTopLevelWindow(display);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
MultiscreenHandler.init();
|
||||
this.MultiscreenHandler = MultiscreenHandler;
|
||||
@@ -18,7 +18,7 @@ function log(msg) {
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
let librecovery = (function() {
|
||||
var librecovery = (function() {
|
||||
let library;
|
||||
try {
|
||||
library = ctypes.open("librecovery.so");
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["SafeMode"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/Webapps.jsm");
|
||||
|
||||
const kSafeModePref = "b2g.safe_mode";
|
||||
const kSafeModePage = "safe_mode.html";
|
||||
|
||||
function debug(aStr) {
|
||||
//dump("-*- SafeMode: " + aStr + "\n");
|
||||
}
|
||||
|
||||
// This module is responsible for checking whether we want to start in safe
|
||||
// mode or not. The flow is as follow:
|
||||
// - wait for the `b2g.safe_mode` preference to be set to something different
|
||||
// than `unset` by nsAppShell
|
||||
// - If it's set to `no`, just start normally.
|
||||
// - If it's set to `yes`, we load a stripped down system app from safe_mode.html"
|
||||
// - This page is responsible to dispatch a mozContentEvent to us.
|
||||
// - If the user choose SafeMode, we disable all add-ons.
|
||||
// - We go on with startup.
|
||||
|
||||
this.SafeMode = {
|
||||
// Returns a promise that resolves when nsAppShell has set the
|
||||
// b2g.safe_mode_state_ready preference to `true`.
|
||||
_waitForPref: function() {
|
||||
debug("waitForPref");
|
||||
try {
|
||||
let currentMode = Services.prefs.getCharPref(kSafeModePref);
|
||||
debug("current mode: " + currentMode);
|
||||
if (currentMode !== "unset") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
} catch(e) { debug("No current mode available!"); }
|
||||
|
||||
// Wait for the preference to toggle.
|
||||
return new Promise((aResolve, aReject) => {
|
||||
let observer = function(aSubject, aTopic, aData) {
|
||||
if (Services.prefs.getCharPref(kSafeModePref)) {
|
||||
Services.prefs.removeObserver(kSafeModePref, observer, false);
|
||||
aResolve();
|
||||
}
|
||||
}
|
||||
|
||||
Services.prefs.addObserver(kSafeModePref, observer, false);
|
||||
});
|
||||
},
|
||||
|
||||
// Resolves once the user has decided how to start.
|
||||
// Note that all the actions happen here, so there is no other action from
|
||||
// consumers than to go on.
|
||||
_waitForUser: function() {
|
||||
debug("waitForUser");
|
||||
let isSafeMode = Services.prefs.getCharPref(kSafeModePref) === "yes";
|
||||
if (!isSafeMode) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
debug("Starting in Safe Mode!");
|
||||
|
||||
// Load $system_app/safe_mode.html as a full screen iframe, and wait for
|
||||
// the user to make a choice.
|
||||
return DOMApplicationRegistry.registryReady.then(() => {
|
||||
let shell = SafeMode.window.shell;
|
||||
let document = SafeMode.window.document;
|
||||
|
||||
let url = Services.io.newURI(shell.homeURL, null, null)
|
||||
.resolve(kSafeModePage);
|
||||
debug("Registry is ready, loading " + url);
|
||||
let frame = document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
|
||||
frame.setAttribute("mozbrowser", "true");
|
||||
frame.setAttribute("mozapp", shell.manifestURL);
|
||||
frame.setAttribute("id", "systemapp"); // To keep screen.js happy.
|
||||
let contentBrowser = document.body.appendChild(frame);
|
||||
|
||||
return new Promise((aResolve, aReject) => {
|
||||
let content = contentBrowser.contentWindow;
|
||||
|
||||
// Stripped down version of the system app bootstrap.
|
||||
function handleEvent(e) {
|
||||
switch(e.type) {
|
||||
case "mozbrowserloadstart":
|
||||
if (content.document.location == "about:blank") {
|
||||
contentBrowser.addEventListener("mozbrowserlocationchange", handleEvent, true);
|
||||
contentBrowser.removeEventListener("mozbrowserloadstart", handleEvent, true);
|
||||
return;
|
||||
}
|
||||
|
||||
notifyContentStart();
|
||||
break;
|
||||
case "mozbrowserlocationchange":
|
||||
if (content.document.location == "about:blank") {
|
||||
return;
|
||||
}
|
||||
|
||||
contentBrowser.removeEventListener("mozbrowserlocationchange", handleEvent, true);
|
||||
notifyContentStart();
|
||||
break;
|
||||
case "mozContentEvent":
|
||||
content.removeEventListener("mozContentEvent", handleEvent, true);
|
||||
contentBrowser.parentNode.removeChild(contentBrowser);
|
||||
|
||||
if (e.detail == "safemode-yes") {
|
||||
// Really starting in safe mode, let's disable add-ons first.
|
||||
DOMApplicationRegistry.disableAllAddons().then(aResolve);
|
||||
} else {
|
||||
aResolve();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function notifyContentStart() {
|
||||
let window = SafeMode.window;
|
||||
window.shell.sendEvent(window, "SafeModeStart");
|
||||
contentBrowser.setVisible(true);
|
||||
|
||||
// browser-ui-startup-complete is used by the AppShell to stop the
|
||||
// boot animation and start gecko rendering.
|
||||
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
|
||||
content.addEventListener("mozContentEvent", handleEvent, true);
|
||||
}
|
||||
|
||||
contentBrowser.addEventListener("mozbrowserloadstart", handleEvent, true);
|
||||
contentBrowser.src = url;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Returns a Promise that resolves once we have decided to run in safe mode
|
||||
// or not. All the safe mode switching actions happen before resolving the
|
||||
// promise.
|
||||
check: function(aWindow) {
|
||||
debug("check");
|
||||
this.window = aWindow;
|
||||
if (AppConstants.platform !== "gonk") {
|
||||
// For now we only have gonk support.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this._waitForPref().then(this._waitForUser);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", "resource://gre/module
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['Screenshot'];
|
||||
|
||||
let Screenshot = {
|
||||
var Screenshot = {
|
||||
get: function screenshot_get() {
|
||||
let systemAppFrame = SystemAppProxy.getFrame();
|
||||
let window = systemAppFrame.ownerDocument.defaultView;
|
||||
|
||||
@@ -94,7 +94,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
|
||||
// There's no point in setting up an observer to monitor the pref, as b2g prefs
|
||||
// can only be overwritten when the profie is recreated. So just get the value
|
||||
// on start-up.
|
||||
let kPersonaUri = "https://firefoxos.persona.org";
|
||||
var kPersonaUri = "https://firefoxos.persona.org";
|
||||
try {
|
||||
kPersonaUri = Services.prefs.getCharPref("toolkit.identity.uri");
|
||||
} catch(noSuchPref) {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
let Ci = Components.interfaces;
|
||||
let Cu = Components.utils;
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
@@ -12,7 +12,7 @@ Cu.import('resource://gre/modules/DOMRequestHelper.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'GlobalSimulatorScreen',
|
||||
'resource://gre/modules/GlobalSimulatorScreen.jsm');
|
||||
|
||||
let DEBUG_PREFIX = 'SimulatorScreen.js - ';
|
||||
var DEBUG_PREFIX = 'SimulatorScreen.js - ';
|
||||
function debug() {
|
||||
//dump(DEBUG_PREFIX + Array.slice(arguments) + '\n');
|
||||
}
|
||||
|
||||
@@ -11,10 +11,12 @@ Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['SystemAppProxy'];
|
||||
|
||||
let SystemAppProxy = {
|
||||
var SystemAppProxy = {
|
||||
_frame: null,
|
||||
_isLoaded: false,
|
||||
_isReady: false,
|
||||
_pendingEvents: [],
|
||||
_pendingLoadedEvents: [],
|
||||
_pendingReadyEvents: [],
|
||||
_pendingListeners: [],
|
||||
|
||||
// To call when a new system app iframe is created
|
||||
@@ -34,18 +36,38 @@ let SystemAppProxy = {
|
||||
return this._frame;
|
||||
},
|
||||
|
||||
// To call when the load event of the System app document is triggered.
|
||||
// i.e. everything that is not lazily loaded are run and done.
|
||||
setIsLoaded: function () {
|
||||
if (this._isLoaded) {
|
||||
Cu.reportError('SystemApp has already been declared as being loaded.');
|
||||
}
|
||||
this._isLoaded = true;
|
||||
|
||||
// Dispatch all events being queued while the system app was still loading
|
||||
this._pendingLoadedEvents
|
||||
.forEach(([type, details]) =>
|
||||
this._sendCustomEvent(type, details, true));
|
||||
this._pendingLoadedEvents = [];
|
||||
},
|
||||
|
||||
// To call when it is ready to receive events
|
||||
// i.e. when system-message-listener-ready mozContentEvent is sent.
|
||||
setIsReady: function () {
|
||||
if (!this._isLoaded) {
|
||||
Cu.reportError('SystemApp.setIsLoaded() should be called before setIsReady().');
|
||||
}
|
||||
|
||||
if (this._isReady) {
|
||||
Cu.reportError('SystemApp has already been declared as being ready.');
|
||||
}
|
||||
this._isReady = true;
|
||||
|
||||
// Dispatch all events being queued while the system app was still loading
|
||||
this._pendingEvents
|
||||
// Dispatch all events being queued while the system app was still not ready
|
||||
this._pendingReadyEvents
|
||||
.forEach(([type, details]) =>
|
||||
this._sendCustomEvent(type, details));
|
||||
this._pendingEvents = [];
|
||||
this._pendingReadyEvents = [];
|
||||
},
|
||||
|
||||
/*
|
||||
@@ -62,6 +84,9 @@ let SystemAppProxy = {
|
||||
* @param details The event details.
|
||||
* @param noPending Set to true to emit this event even before the system
|
||||
* app is ready.
|
||||
* Event is always pending if the app is not loaded yet.
|
||||
*
|
||||
* @returns event? Dispatched event, or null if the event is pending.
|
||||
*/
|
||||
_sendCustomEvent: function systemApp_sendCustomEvent(type,
|
||||
details,
|
||||
@@ -69,10 +94,22 @@ let SystemAppProxy = {
|
||||
target) {
|
||||
let content = this._frame ? this._frame.contentWindow : null;
|
||||
|
||||
// If the system app isn't loaded yet,
|
||||
// queue events until someone calls setIsLoaded
|
||||
if (!content || !this._isLoaded) {
|
||||
if (noPending) {
|
||||
this._pendingLoadedEvents.push([type, details]);
|
||||
} else {
|
||||
this._pendingReadyEvents.push([type, details]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the system app isn't ready yet,
|
||||
// queue events until someone calls setIsReady
|
||||
if (!content || (!this._isReady && !noPending)) {
|
||||
this._pendingEvents.push([type, details]);
|
||||
if (!this._isReady && !noPending) {
|
||||
this._pendingReadyEvents.push([type, details]);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -87,6 +124,10 @@ let SystemAppProxy = {
|
||||
payload = details ? Cu.cloneInto(details, content) : {};
|
||||
}
|
||||
|
||||
if ((target || content) === this._frame.contentWindow) {
|
||||
dump('XXX FIXME : Dispatch a ' + type + ': ' + details.type + "\n");
|
||||
}
|
||||
|
||||
event.initCustomEvent(type, true, false, payload);
|
||||
(target || content).dispatchEvent(event);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/WebappsUpdater.jsm");
|
||||
|
||||
const VERBOSE = 1;
|
||||
let log =
|
||||
var log =
|
||||
VERBOSE ?
|
||||
function log_dump(msg) { dump("UpdatePrompt: "+ msg +"\n"); } :
|
||||
function log_noop(msg) { };
|
||||
|
||||
@@ -18,6 +18,7 @@ EXTRA_COMPONENTS += [
|
||||
'FxAccountsUIGlue.js',
|
||||
'HelperAppDialog.js',
|
||||
'InterAppCommUIGlue.js',
|
||||
'KillSwitch.js',
|
||||
'MailtoProtocolHandler.js',
|
||||
'MobileIdentityUIGlue.js',
|
||||
'OMAContentHandler.js',
|
||||
@@ -65,7 +66,9 @@ EXTRA_JS_MODULES += [
|
||||
'LogCapture.jsm',
|
||||
'LogParser.jsm',
|
||||
'LogShake.jsm',
|
||||
'MultiscreenHandler.jsm',
|
||||
'OrientationChangeHandler.jsm',
|
||||
'SafeMode.jsm',
|
||||
'Screenshot.jsm',
|
||||
'SignInToWebsite.jsm',
|
||||
'SystemAppProxy.jsm',
|
||||
@@ -73,6 +76,10 @@ EXTRA_JS_MODULES += [
|
||||
'WebappsUpdater.jsm',
|
||||
]
|
||||
|
||||
EXTRA_PP_JS_MODULES += [
|
||||
'KillSwitchMain.jsm'
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
|
||||
EXTRA_JS_MODULES += [
|
||||
'GlobalSimulatorScreen.jsm'
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
let Ci = Components.interfaces;
|
||||
let Cc = Components.classes;
|
||||
let Cu = Components.utils;
|
||||
|
||||
// Stolen from SpecialPowers, since at this point we don't know we're in a test.
|
||||
let isMainProcess = function() {
|
||||
try {
|
||||
return Cc["@mozilla.org/xre/app-info;1"]
|
||||
.getService(Ci.nsIXULRuntime)
|
||||
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
||||
} catch (e) { }
|
||||
return true;
|
||||
};
|
||||
|
||||
var fakeLibcUtils = {
|
||||
_props_: {},
|
||||
property_set: function(name, value) {
|
||||
dump("property_set('" + name + "', '" + value+ "' [" + (typeof value) + "]);\n");
|
||||
this._props_[name] = value;
|
||||
},
|
||||
property_get: function(name, defaultValue) {
|
||||
dump("property_get('" + name + "', '" + defaultValue+ "');\n");
|
||||
if (Object.keys(this._props_).indexOf(name) !== -1) {
|
||||
return this._props_[name];
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var kUserValues;
|
||||
|
||||
function installUserValues(next) {
|
||||
var fakeValues = {
|
||||
settings: {
|
||||
"lockscreen.locked": false,
|
||||
"lockscreen.lock-immediately": false
|
||||
},
|
||||
prefs: {
|
||||
"b2g.killswitch.test": false
|
||||
},
|
||||
properties: {
|
||||
"dalvik.vm.heapmaxfree": "32m",
|
||||
"dalvik.vm.isa.arm.features": "fdiv",
|
||||
"dalvik.vm.lockprof.threshold": "5000",
|
||||
"net.bt.name": "BTAndroid",
|
||||
"dalvik.vm.stack-trace-file": "/data/anr/stack-traces.txt"
|
||||
}
|
||||
};
|
||||
|
||||
OS.File.writeAtomic(kUserValues,
|
||||
JSON.stringify(fakeValues)).then(() => {
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
if (isMainProcess()) {
|
||||
Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/KillSwitchMain.jsm");
|
||||
|
||||
kUserValues = OS.Path.join(OS.Constants.Path.profileDir, "killswitch.json");
|
||||
|
||||
installUserValues(() => {
|
||||
KillSwitchMain._libcutils = fakeLibcUtils;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
function setupSettings(target) {
|
||||
ok((Object.keys(initialSettingsValues).length > 0), "Has at least one setting");
|
||||
|
||||
Object.keys(initialSettingsValues).forEach(k => {
|
||||
ok(Object.keys(target).indexOf(k) !== -1, "Same settings set");
|
||||
});
|
||||
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
lock.set(initialSettingsValues);
|
||||
}
|
||||
|
||||
function testSettingsInitial(next) {
|
||||
var promises = [];
|
||||
for (var setting in initialSettingsValues) {
|
||||
promises.push(navigator.mozSettings.createLock().get(setting));
|
||||
}
|
||||
|
||||
Promise.all(promises).then(values => {
|
||||
values.forEach(set => {
|
||||
var key = Object.keys(set)[0];
|
||||
var value = set[key];
|
||||
is(value, initialSettingsValues[key], "Value of " + key + " is initial one");
|
||||
});
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
function testSettingsExpected(target, next) {
|
||||
var promises = [];
|
||||
for (var setting in initialSettingsValues) {
|
||||
promises.push(navigator.mozSettings.createLock().get(setting));
|
||||
}
|
||||
|
||||
Promise.all(promises).then(values => {
|
||||
values.forEach(set => {
|
||||
var key = Object.keys(set)[0];
|
||||
var value = set[key];
|
||||
is(value, target[key], "Value of " + key + " is expected one");
|
||||
});
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
function testSetPrefValue(prefName, prefValue) {
|
||||
switch (typeof prefValue) {
|
||||
case "boolean":
|
||||
SpecialPowers.setBoolPref(prefName, prefValue);
|
||||
break;
|
||||
|
||||
case "number":
|
||||
SpecialPowers.setIntPref(prefName, prefValue);
|
||||
break;
|
||||
|
||||
case "string":
|
||||
SpecialPowers.setCharPref(prefName, prefValue);
|
||||
break;
|
||||
|
||||
default:
|
||||
is(false, "Unexpected pref type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function testGetPrefValue(prefName, prefValue) {
|
||||
var rv = undefined;
|
||||
|
||||
switch (typeof prefValue) {
|
||||
case "boolean":
|
||||
rv = SpecialPowers.getBoolPref(prefName);
|
||||
break;
|
||||
|
||||
case "number":
|
||||
rv = SpecialPowers.getIntPref(prefName);
|
||||
break;
|
||||
|
||||
case "string":
|
||||
rv = SpecialPowers.getCharPref(prefName);
|
||||
break;
|
||||
|
||||
default:
|
||||
is(false, "Unexpected pref type");
|
||||
break;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
function setupPrefs(target) {
|
||||
ok((Object.keys(initialPrefsValues).length > 0), "Has at least one pref");
|
||||
|
||||
Object.keys(initialPrefsValues).forEach(k => {
|
||||
ok(Object.keys(target).indexOf(k) !== -1, "Same pref set");
|
||||
});
|
||||
|
||||
Object.keys(initialPrefsValues).forEach(key => {
|
||||
testSetPrefValue(key, initialPrefsValues[key]);
|
||||
});
|
||||
}
|
||||
|
||||
function testPrefsInitial() {
|
||||
Object.keys(initialPrefsValues).forEach(key => {
|
||||
var value = testGetPrefValue(key, initialPrefsValues[key]);
|
||||
is(value, initialPrefsValues[key], "Value of " + key + " is initial one");
|
||||
});
|
||||
}
|
||||
|
||||
function testPrefsExpected(target) {
|
||||
Object.keys(target).forEach(key => {
|
||||
var value = testGetPrefValue(key, target[key]);
|
||||
is(value, target[key], "Value of " + key + " is initial one");
|
||||
});
|
||||
}
|
||||
|
||||
function finish() {
|
||||
SpecialPowers.removePermission("killswitch", document);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function addPermissions() {
|
||||
if (SpecialPowers.hasPermission("killswitch", document)) {
|
||||
startTests();
|
||||
} else {
|
||||
var allow = SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
[ "killswitch", "settings-api-read", "settings-api-write",
|
||||
"settings-read", "settings-write", "settings-clear"
|
||||
].forEach(perm => {
|
||||
SpecialPowers.addPermission(perm, allow, document);
|
||||
});
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
var url = SimpleTest.getTestFileURL("file_loadserver.js");
|
||||
var script = SpecialPowers.loadChromeScript(url);
|
||||
}
|
||||
|
||||
function addPrefs() {
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.ignore_webidl_scope_checks", true],
|
||||
["dom.mozKillSwitch.enabled", true],
|
||||
]}, addPermissions);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
[DEFAULT]
|
||||
skip-if = toolkit != "gonk"
|
||||
support-files =
|
||||
permission_handler_chrome.js
|
||||
SandboxPromptTest.html
|
||||
@@ -8,14 +7,27 @@ support-files =
|
||||
systemapp_helper.js
|
||||
presentation_prompt_handler_chrome.js
|
||||
presentation_ui_glue_handler_chrome.js
|
||||
file_loadserver.js
|
||||
killswitch.js
|
||||
|
||||
[test_filepicker_path.html]
|
||||
skip-if = toolkit != "gonk"
|
||||
[test_permission_deny.html]
|
||||
skip-if = toolkit != "gonk"
|
||||
[test_permission_gum_remember.html]
|
||||
skip-if = true # Bug 1019572 - frequent timeouts
|
||||
[test_sandbox_permission.html]
|
||||
skip-if = toolkit != "gonk"
|
||||
[test_screenshot.html]
|
||||
skip-if = toolkit != "gonk"
|
||||
[test_systemapp.html]
|
||||
skip-if = toolkit != "gonk"
|
||||
[test_presentation_device_prompt.html]
|
||||
skip-if = toolkit != "gonk"
|
||||
[test_permission_visibilitychange.html]
|
||||
skip-if = toolkit != "gonk"
|
||||
[test_presentation_request_ui_glue.html]
|
||||
skip-if = toolkit != "gonk"
|
||||
[test_killswitch_basics.html]
|
||||
[test_killswitch_disable.html]
|
||||
[test_killswitch_enable.html]
|
||||
|
||||
@@ -3,14 +3,14 @@ const Cu = Components.utils;
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Load a duplicated copy of the jsm to prevent messing with the currently running one
|
||||
let scope = {};
|
||||
var scope = {};
|
||||
Services.scriptloader.loadSubScript("resource://gre/modules/SystemAppProxy.jsm", scope);
|
||||
const { SystemAppProxy } = scope;
|
||||
|
||||
let frame;
|
||||
let customEventTarget;
|
||||
var frame;
|
||||
var customEventTarget;
|
||||
|
||||
let index = -1;
|
||||
var index = -1;
|
||||
function next() {
|
||||
index++;
|
||||
if (index >= steps.length) {
|
||||
@@ -26,11 +26,12 @@ function next() {
|
||||
|
||||
// Listen for events received by the system app document
|
||||
// to ensure that we receive all of them, in an expected order and time
|
||||
let isLoaded = false;
|
||||
let n = 0;
|
||||
var isLoaded = false;
|
||||
var isReady = false;
|
||||
var n = 0;
|
||||
function listener(event) {
|
||||
if (!isLoaded) {
|
||||
assert.ok(false, "Received event before the iframe is ready");
|
||||
assert.ok(false, "Received event before the iframe is loaded");
|
||||
return;
|
||||
}
|
||||
n++;
|
||||
@@ -41,16 +42,34 @@ function listener(event) {
|
||||
assert.equal(event.type, "custom");
|
||||
assert.equal(event.detail.name, "second");
|
||||
|
||||
next(); // call checkEventDispatching
|
||||
next(); // call checkEventPendingBeforeLoad
|
||||
} else if (n == 3) {
|
||||
if (!isReady) {
|
||||
assert.ok(false, "Received event before the iframe is loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
assert.equal(event.type, "custom");
|
||||
assert.equal(event.detail.name, "third");
|
||||
} else if (n == 4) {
|
||||
if (!isReady) {
|
||||
assert.ok(false, "Received event before the iframe is loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
assert.equal(event.type, "mozChromeEvent");
|
||||
assert.equal(event.detail.name, "fourth");
|
||||
|
||||
next(); // call checkEventDispatching
|
||||
} else if (n == 5) {
|
||||
assert.equal(event.type, "custom");
|
||||
assert.equal(event.detail.name, "fifth");
|
||||
} else if (n === 6) {
|
||||
assert.equal(event.type, "mozChromeEvent");
|
||||
assert.equal(event.detail.name, "sixth");
|
||||
} else if (n === 7) {
|
||||
assert.equal(event.type, "custom");
|
||||
assert.equal(event.detail.name, "seventh");
|
||||
assert.equal(event.target, customEventTarget);
|
||||
|
||||
next(); // call checkEventListening();
|
||||
@@ -60,7 +79,7 @@ function listener(event) {
|
||||
}
|
||||
|
||||
|
||||
let steps = [
|
||||
var steps = [
|
||||
function waitForWebapps() {
|
||||
// We are using webapps API later in this test and we need to ensure
|
||||
// it is fully initialized before trying to use it
|
||||
@@ -72,8 +91,8 @@ let steps = [
|
||||
|
||||
function earlyEvents() {
|
||||
// Immediately try to send events
|
||||
SystemAppProxy.dispatchEvent({ name: "first" });
|
||||
SystemAppProxy._sendCustomEvent("custom", { name: "second" });
|
||||
SystemAppProxy._sendCustomEvent("mozChromeEvent", { name: "first" }, true);
|
||||
SystemAppProxy._sendCustomEvent("custom", { name: "second" }, true);
|
||||
next();
|
||||
},
|
||||
|
||||
@@ -110,7 +129,7 @@ let steps = [
|
||||
// Declare that the iframe is now loaded.
|
||||
// That should dispatch early events
|
||||
isLoaded = true;
|
||||
SystemAppProxy.setIsReady();
|
||||
SystemAppProxy.setIsLoaded();
|
||||
assert.ok(true, "Frame declared as loaded");
|
||||
|
||||
let gotFrame = SystemAppProxy.getFrame();
|
||||
@@ -123,13 +142,24 @@ let steps = [
|
||||
frame.setAttribute("src", "data:text/html,system app");
|
||||
},
|
||||
|
||||
function checkEventPendingBeforeLoad() {
|
||||
// Frame is loaded but not ready,
|
||||
// these events should queue before the System app is ready.
|
||||
SystemAppProxy._sendCustomEvent("custom", { name: "third" });
|
||||
SystemAppProxy.dispatchEvent({ name: "fourth" });
|
||||
|
||||
isReady = true;
|
||||
SystemAppProxy.setIsReady();
|
||||
// Once this 4th event is received, we will run checkEventDispatching
|
||||
},
|
||||
|
||||
function checkEventDispatching() {
|
||||
// Send events after the iframe is ready,
|
||||
// they should be dispatched right away
|
||||
SystemAppProxy._sendCustomEvent("custom", { name: "third" });
|
||||
SystemAppProxy.dispatchEvent({ name: "fourth" });
|
||||
SystemAppProxy._sendCustomEvent("custom", { name: "fifth" }, false, customEventTarget);
|
||||
// Once this 5th event is received, we will run checkEventListening
|
||||
SystemAppProxy._sendCustomEvent("custom", { name: "fifth" });
|
||||
SystemAppProxy.dispatchEvent({ name: "sixth" });
|
||||
SystemAppProxy._sendCustomEvent("custom", { name: "seventh" }, false, customEventTarget);
|
||||
// Once this 7th event is received, we will run checkEventListening
|
||||
},
|
||||
|
||||
function checkEventListening() {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Enabling of killswitch feature</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
"use strict";
|
||||
|
||||
function testNotExposed(check) {
|
||||
ok(!("mozKillSwitch" in navigator),
|
||||
"mozKillSwitch not exposed in navigator: " + check);
|
||||
}
|
||||
|
||||
function testIsExposed() {
|
||||
ok(("mozKillSwitch" in navigator), "mozKillSwitch is exposed in navigator");
|
||||
ok(("enable" in navigator.mozKillSwitch), "mozKillSwitch has |enable|");
|
||||
ok(("disable" in navigator.mozKillSwitch), "mozKillSwitch has |disable|");
|
||||
}
|
||||
|
||||
function continueTests() {
|
||||
// Now we should have it!
|
||||
testIsExposed();
|
||||
finish();
|
||||
}
|
||||
|
||||
function finish() {
|
||||
SpecialPowers.removePermission("killswitch", document);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function alreadyHasPermission() {
|
||||
return SpecialPowers.hasPermission("killswitch", document);
|
||||
}
|
||||
|
||||
function addPermission() {
|
||||
var allow = SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
SpecialPowers.addPermission("killswitch", allow, document);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function addPrefIgnoreWebIDL(next) {
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.ignore_webidl_scope_checks", true],
|
||||
]}, next);
|
||||
}
|
||||
|
||||
function addPrefKillSwitch(next) {
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.mozKillSwitch.enabled", true],
|
||||
]}, next);
|
||||
}
|
||||
|
||||
function startTests() {
|
||||
if (alreadyHasPermission()) {
|
||||
continueTests();
|
||||
} else {
|
||||
// Make sure it's not exposed
|
||||
testNotExposed("webidl, pref, perm");
|
||||
|
||||
// Expose certified APIs
|
||||
addPrefIgnoreWebIDL(() => {
|
||||
// Still not exposed because not perm and pref
|
||||
testNotExposed("pref, perm");
|
||||
|
||||
// Add the kill switch pref
|
||||
addPrefKillSwitch(() => {
|
||||
// Still not exposed because not perm
|
||||
testNotExposed("perm");
|
||||
|
||||
// Will reload the page
|
||||
addPermission();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
startTests();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Disabling of killswitch feature</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
|
||||
</script>
|
||||
<script type="application/javascript" src="killswitch.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
"use strict";
|
||||
|
||||
var initialSettingsValues = {
|
||||
"lockscreen.locked": true,
|
||||
"lockscreen.lock-immediately": true
|
||||
};
|
||||
|
||||
var initialPrefsValues = {
|
||||
"b2g.killswitch.test": true
|
||||
};
|
||||
|
||||
var disabledSettingsExpected = {
|
||||
"lockscreen.locked": false,
|
||||
"lockscreen.lock-immediately": false
|
||||
};
|
||||
|
||||
var disabledPrefsExpected = {
|
||||
"b2g.killswitch.test": false
|
||||
};
|
||||
|
||||
function testDoAction() {
|
||||
return navigator.mozKillSwitch.disable();
|
||||
}
|
||||
|
||||
function startTests() {
|
||||
setupSettings(disabledSettingsExpected);
|
||||
setupPrefs(initialPrefsValues);
|
||||
testSettingsInitial(() => {
|
||||
testPrefsInitial();
|
||||
testDoAction().then(() => {
|
||||
testSettingsExpected(disabledSettingsExpected, () => {
|
||||
testPrefsExpected(disabledPrefsExpected);
|
||||
finish();
|
||||
});
|
||||
}).catch(() => {
|
||||
ok(false, "KillSwitch promise failed");
|
||||
finish();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
loadSettings();
|
||||
addPrefs();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Enabling of killswitch feature</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
|
||||
</script>
|
||||
<script type="application/javascript" src="killswitch.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
"use strict";
|
||||
|
||||
var initialSettingsValues = {
|
||||
"lockscreen.locked": false,
|
||||
"lockscreen.lock-immediately": false
|
||||
};
|
||||
|
||||
var initialPrefsValues = {
|
||||
"b2g.killswitch.test": false
|
||||
};
|
||||
|
||||
var enabledSettingsExpected = {
|
||||
"lockscreen.locked": true,
|
||||
"lockscreen.lock-immediately": true
|
||||
};
|
||||
|
||||
var enabledPrefsExpected = {
|
||||
"b2g.killswitch.test": true
|
||||
};
|
||||
|
||||
function testDoAction() {
|
||||
return navigator.mozKillSwitch.enable();
|
||||
}
|
||||
|
||||
function startTests() {
|
||||
setupSettings(enabledSettingsExpected);
|
||||
setupPrefs(initialPrefsValues);
|
||||
testSettingsInitial(() => {
|
||||
testPrefsInitial();
|
||||
testDoAction().then(() => {
|
||||
testSettingsExpected(enabledSettingsExpected, () => {
|
||||
testPrefsExpected(enabledPrefsExpected);
|
||||
finish();
|
||||
});
|
||||
}).catch(() => {
|
||||
ok(false, "KillSwitch promise failed");
|
||||
finish();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
loadSettings();
|
||||
addPrefs();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,425 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
|
||||
let kUserValues;
|
||||
|
||||
function run_test() {
|
||||
do_get_profile();
|
||||
Cu.import("resource://gre/modules/KillSwitchMain.jsm");
|
||||
|
||||
check_enabledValues();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function check_enabledValues() {
|
||||
let expected = {
|
||||
settings: {
|
||||
"debugger.remote-mode": "disabled",
|
||||
"developer.menu.enabled": false,
|
||||
"devtools.unrestricted": false,
|
||||
"lockscreen.enabled": true,
|
||||
"lockscreen.locked": true,
|
||||
"lockscreen.lock-immediately": true,
|
||||
"tethering.usb.enabled": false,
|
||||
"tethering.wifi.enabled": false,
|
||||
"ums.enabled": false
|
||||
},
|
||||
prefs: {
|
||||
"b2g.killswitch.test": true
|
||||
},
|
||||
properties: {
|
||||
"persist.sys.usb.config": "none"
|
||||
},
|
||||
services: {
|
||||
"adbd": "stop"
|
||||
}
|
||||
};
|
||||
|
||||
for (let key of Object.keys(KillSwitchMain._enabledValues.settings)) {
|
||||
strictEqual(expected.settings[key],
|
||||
KillSwitchMain._enabledValues.settings[key],
|
||||
"setting " + key);
|
||||
}
|
||||
|
||||
for (let key of Object.keys(KillSwitchMain._enabledValues.prefs)) {
|
||||
strictEqual(expected.prefs[key],
|
||||
KillSwitchMain._enabledValues.prefs[key],
|
||||
"pref " + key);
|
||||
}
|
||||
|
||||
for (let key of Object.keys(KillSwitchMain._enabledValues.properties)) {
|
||||
strictEqual(expected.properties[key],
|
||||
KillSwitchMain._enabledValues.properties[key],
|
||||
"proprety " + key);
|
||||
}
|
||||
|
||||
for (let key of Object.keys(KillSwitchMain._enabledValues.services)) {
|
||||
strictEqual(expected.services[key],
|
||||
KillSwitchMain._enabledValues.services[key],
|
||||
"service " + key);
|
||||
}
|
||||
}
|
||||
|
||||
add_test(function test_prepareTestValues() {
|
||||
if (("adbd" in KillSwitchMain._enabledValues.services)) {
|
||||
strictEqual(KillSwitchMain._enabledValues.services["adbd"], "stop");
|
||||
|
||||
// We replace those for the test because on Gonk we will loose the control
|
||||
KillSwitchMain._enabledValues.settings = {
|
||||
"lockscreen.locked": true,
|
||||
"lockscreen.lock-immediately": true
|
||||
};
|
||||
KillSwitchMain._enabledValues.services = {};
|
||||
KillSwitchMain._enabledValues.properties = {
|
||||
"dalvik.vm.heapmaxfree": "8m",
|
||||
"dalvik.vm.isa.arm.features": "div",
|
||||
"dalvik.vm.lockprof.threshold": "500",
|
||||
"net.bt.name": "Android",
|
||||
"dalvik.vm.stack-trace-file": "/data/anr/traces.txt"
|
||||
}
|
||||
}
|
||||
|
||||
kUserValues = OS.Path.join(OS.Constants.Path.profileDir, "killswitch.json");
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
function reset_status() {
|
||||
KillSwitchMain._ksState = undefined;
|
||||
KillSwitchMain._libcutils.property_set("persist.moz.killswitch", "undefined");
|
||||
}
|
||||
|
||||
function install_common_tests() {
|
||||
add_test(function test_readStateProperty() {
|
||||
KillSwitchMain._libcutils.property_set("persist.moz.killswitch", "false");
|
||||
KillSwitchMain.readStateProperty();
|
||||
strictEqual(KillSwitchMain._ksState, false);
|
||||
|
||||
KillSwitchMain._libcutils.property_set("persist.moz.killswitch", "true");
|
||||
KillSwitchMain.readStateProperty();
|
||||
strictEqual(KillSwitchMain._ksState, true);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_writeStateProperty() {
|
||||
KillSwitchMain._ksState = false;
|
||||
KillSwitchMain.writeStateProperty();
|
||||
let state = KillSwitchMain._libcutils.property_get("persist.moz.killswitch");
|
||||
strictEqual(state, "false");
|
||||
|
||||
KillSwitchMain._ksState = true;
|
||||
KillSwitchMain.writeStateProperty();
|
||||
state = KillSwitchMain._libcutils.property_get("persist.moz.killswitch");
|
||||
strictEqual(state, "true");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_doEnable() {
|
||||
reset_status();
|
||||
|
||||
let enable = KillSwitchMain.doEnable();
|
||||
ok(enable, "should have a Promise");
|
||||
|
||||
enable.then(() => {
|
||||
strictEqual(KillSwitchMain._ksState, true);
|
||||
let state = KillSwitchMain._libcutils.property_get("persist.moz.killswitch");
|
||||
strictEqual(state, "true");
|
||||
|
||||
run_next_test();
|
||||
}).catch(err => {
|
||||
ok(false, "should have succeeded");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_enableMessage() {
|
||||
reset_status();
|
||||
|
||||
let listener = {
|
||||
assertPermission: function() {
|
||||
return true;
|
||||
},
|
||||
sendAsyncMessage: function(name, data) {
|
||||
strictEqual(name, "KillSwitch:Enable:OK");
|
||||
strictEqual(data.requestID, 1);
|
||||
|
||||
let state = KillSwitchMain._libcutils.property_get("persist.moz.killswitch");
|
||||
strictEqual(state, "true");
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
};
|
||||
|
||||
KillSwitchMain.receiveMessage({
|
||||
name: "KillSwitch:Enable",
|
||||
target: listener,
|
||||
data: {
|
||||
requestID: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_saveUserValues_prepare() {
|
||||
reset_status();
|
||||
|
||||
OS.File.exists(kUserValues).then(e => {
|
||||
if (e) {
|
||||
OS.File.remove(kUserValues).then(() => {
|
||||
run_next_test();
|
||||
}).catch(err => {
|
||||
ok(false, "should have succeeded");
|
||||
run_next_test();
|
||||
});
|
||||
} else {
|
||||
run_next_test();
|
||||
}
|
||||
}).catch(err => {
|
||||
ok(false, "should have succeeded");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_saveUserValues() {
|
||||
reset_status();
|
||||
|
||||
let expectedValues = Object.assign({}, KillSwitchMain._enabledValues);
|
||||
|
||||
// Reset _enabledValues so we check properly that the dumped state
|
||||
// is the current device state
|
||||
KillSwitchMain._enabledValues = {
|
||||
settings: {
|
||||
"lockscreen.locked": false,
|
||||
"lockscreen.lock-immediately": false
|
||||
},
|
||||
prefs: {
|
||||
"b2g.killswitch.test": false
|
||||
},
|
||||
properties: {
|
||||
"dalvik.vm.heapmaxfree": "32m",
|
||||
"dalvik.vm.isa.arm.features": "fdiv",
|
||||
"dalvik.vm.lockprof.threshold": "5000",
|
||||
"net.bt.name": "BTAndroid",
|
||||
"dalvik.vm.stack-trace-file": "/data/anr/stack-traces.txt"
|
||||
},
|
||||
services: {}
|
||||
};
|
||||
|
||||
KillSwitchMain.saveUserValues().then(e => {
|
||||
ok(e, "should have succeeded");
|
||||
|
||||
OS.File.read(kUserValues, { encoding: "utf-8" }).then(content => {
|
||||
let obj = JSON.parse(content);
|
||||
|
||||
deepEqual(obj.settings, expectedValues.settings);
|
||||
notDeepEqual(obj.settings, KillSwitchMain._enabledValues.settings);
|
||||
|
||||
deepEqual(obj.prefs, expectedValues.prefs);
|
||||
notDeepEqual(obj.prefs, KillSwitchMain._enabledValues.prefs);
|
||||
|
||||
deepEqual(obj.properties, expectedValues.properties);
|
||||
notDeepEqual(obj.properties, KillSwitchMain._enabledValues.properties);
|
||||
|
||||
run_next_test();
|
||||
}).catch(err => {
|
||||
ok(false, "should have succeeded");
|
||||
run_next_test();
|
||||
});
|
||||
}).catch(err => {
|
||||
ok(false, "should have succeeded");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_saveUserValues_cleaup() {
|
||||
reset_status();
|
||||
|
||||
OS.File.exists(kUserValues).then(e => {
|
||||
if (e) {
|
||||
OS.File.remove(kUserValues).then(() => {
|
||||
ok(true, "should have had a file");
|
||||
run_next_test();
|
||||
}).catch(err => {
|
||||
ok(false, "should have succeeded");
|
||||
run_next_test();
|
||||
});
|
||||
} else {
|
||||
ok(false, "should have had a file");
|
||||
run_next_test();
|
||||
}
|
||||
}).catch(err => {
|
||||
ok(false, "should have succeeded");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_restoreUserValues_prepare() {
|
||||
reset_status();
|
||||
|
||||
let fakeValues = {
|
||||
settings: {
|
||||
"lockscreen.locked": false,
|
||||
"lockscreen.lock-immediately": false
|
||||
},
|
||||
prefs: {
|
||||
"b2g.killswitch.test": false
|
||||
},
|
||||
properties: {
|
||||
"dalvik.vm.heapmaxfree": "32m",
|
||||
"dalvik.vm.isa.arm.features": "fdiv",
|
||||
"dalvik.vm.lockprof.threshold": "5000",
|
||||
"net.bt.name": "BTAndroid",
|
||||
"dalvik.vm.stack-trace-file": "/data/anr/stack-traces.txt"
|
||||
}
|
||||
};
|
||||
|
||||
OS.File.exists(kUserValues).then(e => {
|
||||
if (!e) {
|
||||
OS.File.writeAtomic(kUserValues,
|
||||
JSON.stringify(fakeValues)).then(() => {
|
||||
ok(true, "success writing file");
|
||||
run_next_test();
|
||||
}, err => {
|
||||
ok(false, "error writing file");
|
||||
run_next_test();
|
||||
});
|
||||
} else {
|
||||
ok(false, "file should not have been there");
|
||||
run_next_test();
|
||||
}
|
||||
}).catch(err => {
|
||||
ok(false, "should have succeeded");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_restoreUserValues() {
|
||||
reset_status();
|
||||
|
||||
KillSwitchMain.restoreUserValues().then(e => {
|
||||
ok(e, "should have succeeded");
|
||||
|
||||
strictEqual(e.settings["lockscreen.locked"], false);
|
||||
strictEqual(e.settings["lockscreen.lock-immediately"], false);
|
||||
|
||||
strictEqual(e.prefs["b2g.killswitch.test"], false);
|
||||
|
||||
strictEqual(
|
||||
e.properties["dalvik.vm.heapmaxfree"],
|
||||
"32m");
|
||||
strictEqual(
|
||||
e.properties["dalvik.vm.isa.arm.features"],
|
||||
"fdiv");
|
||||
strictEqual(
|
||||
e.properties["dalvik.vm.lockprof.threshold"],
|
||||
"5000");
|
||||
strictEqual(
|
||||
e.properties["net.bt.name"],
|
||||
"BTAndroid");
|
||||
strictEqual(
|
||||
e.properties["dalvik.vm.stack-trace-file"],
|
||||
"/data/anr/stack-traces.txt");
|
||||
|
||||
strictEqual(
|
||||
KillSwitchMain._libcutils.property_get("dalvik.vm.heapmaxfree"),
|
||||
"32m");
|
||||
strictEqual(
|
||||
KillSwitchMain._libcutils.property_get("dalvik.vm.isa.arm.features"),
|
||||
"fdiv");
|
||||
strictEqual(
|
||||
KillSwitchMain._libcutils.property_get("dalvik.vm.lockprof.threshold"),
|
||||
"5000");
|
||||
strictEqual(
|
||||
KillSwitchMain._libcutils.property_get("net.bt.name"),
|
||||
"BTAndroid");
|
||||
strictEqual(
|
||||
KillSwitchMain._libcutils.property_get("dalvik.vm.stack-trace-file"),
|
||||
"/data/anr/stack-traces.txt");
|
||||
|
||||
run_next_test();
|
||||
}).catch(err => {
|
||||
ok(false, "should not have had an error");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_doDisable() {
|
||||
reset_status();
|
||||
|
||||
let disable = KillSwitchMain.doDisable()
|
||||
ok(disable, "should have a Promise");
|
||||
|
||||
disable.then(() => {
|
||||
strictEqual(KillSwitchMain._ksState, false);
|
||||
let state = KillSwitchMain._libcutils.property_get("persist.moz.killswitch");
|
||||
strictEqual(state, "false");
|
||||
|
||||
run_next_test();
|
||||
}).catch(err => {
|
||||
ok(false, "should have succeeded");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_disableMessage() {
|
||||
reset_status();
|
||||
|
||||
let listener = {
|
||||
assertPermission: function() {
|
||||
return true;
|
||||
},
|
||||
sendAsyncMessage: function(name, data) {
|
||||
strictEqual(name, "KillSwitch:Disable:OK");
|
||||
strictEqual(data.requestID, 2);
|
||||
|
||||
let state = KillSwitchMain._libcutils.property_get("persist.moz.killswitch");
|
||||
strictEqual(state, "false");
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
};
|
||||
|
||||
KillSwitchMain.receiveMessage({
|
||||
name: "KillSwitch:Disable",
|
||||
target: listener,
|
||||
data: {
|
||||
requestID: 2
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_doEnable_only_once() {
|
||||
reset_status();
|
||||
|
||||
let firstEnable = KillSwitchMain.doEnable();
|
||||
ok(firstEnable, "should have a first Promise");
|
||||
|
||||
firstEnable.then(() => {
|
||||
let secondEnable = KillSwitchMain.doEnable();
|
||||
ok(secondEnable, "should have a second Promise");
|
||||
|
||||
secondEnable.then(() => {
|
||||
ok(false, "second enable should have not succeeded");
|
||||
run_next_test();
|
||||
}).catch(err => {
|
||||
strictEqual(err, true, "second enable should reject(true);");
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
}).catch(err => {
|
||||
ok(false, "first enable should have succeeded");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Boostrap LogShake's tests that need gonk support.
|
||||
* This is creating a fake sdcard for LogShake tests and importing LogShake and
|
||||
* osfile
|
||||
*/
|
||||
|
||||
/* jshint moz: true */
|
||||
/* global Components, LogCapture, LogShake, ok, add_test, run_next_test, dump,
|
||||
do_get_profile, OS, volumeService, equal, XPCOMUtils */
|
||||
/* exported setup_logshake_mocks */
|
||||
|
||||
/* disable use strict warning */
|
||||
/* jshint -W097 */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
|
||||
"@mozilla.org/telephony/volume-service;1",
|
||||
"nsIVolumeService");
|
||||
|
||||
let sdcard;
|
||||
|
||||
function setup_logshake_mocks() {
|
||||
do_get_profile();
|
||||
setup_fs();
|
||||
}
|
||||
|
||||
function setup_fs() {
|
||||
OS.File.makeDir("/data/local/tmp/sdcard/", {from: "/data"}).then(function() {
|
||||
setup_sdcard();
|
||||
});
|
||||
}
|
||||
|
||||
function setup_sdcard() {
|
||||
let volName = "sdcard";
|
||||
let mountPoint = "/data/local/tmp/sdcard";
|
||||
volumeService.createFakeVolume(volName, mountPoint);
|
||||
|
||||
let vol = volumeService.getVolumeByName(volName);
|
||||
ok(vol, "volume shouldn't be null");
|
||||
equal(volName, vol.name, "name");
|
||||
equal(Ci.nsIVolume.STATE_MOUNTED, vol.state, "state");
|
||||
|
||||
ensure_sdcard();
|
||||
}
|
||||
|
||||
function ensure_sdcard() {
|
||||
sdcard = volumeService.getVolumeByName("sdcard").mountPoint;
|
||||
ok(sdcard, "Should have a valid sdcard mountpoint");
|
||||
run_next_test();
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {results: Cr} = Components;
|
||||
|
||||
// Trivial test just to make sure we have no syntax error
|
||||
add_test(function test_ksm_ok() {
|
||||
ok(KillSwitchMain, "KillSwitchMain object exists");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
let aMessageNoPerm = {
|
||||
name: "KillSwitch:Enable",
|
||||
target: {
|
||||
assertPermission: function() {
|
||||
return false;
|
||||
},
|
||||
killChild: function() { }
|
||||
}
|
||||
};
|
||||
|
||||
let aMessageWithPerm = {
|
||||
name: "KillSwitch:Enable",
|
||||
target: {
|
||||
assertPermission: function() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requestID: 0
|
||||
}
|
||||
};
|
||||
|
||||
add_test(function test_sendMessageWithoutPerm() {
|
||||
try {
|
||||
KillSwitchMain.receiveMessage(aMessageNoPerm);
|
||||
ok(false, "Should have failed");
|
||||
} catch (ex) {
|
||||
// strictEqual(ex, Cr.NS_ERROR_NOT_AVAILABLE);
|
||||
}
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_sendMessageWithPerm() {
|
||||
let rv = KillSwitchMain.receiveMessage(aMessageWithPerm);
|
||||
strictEqual(rv, undefined);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
let uMessage = {
|
||||
name: "KillSwitch:WTF",
|
||||
target: {
|
||||
assertPermission: function() {
|
||||
return true;
|
||||
},
|
||||
killChild: function() { }
|
||||
}
|
||||
};
|
||||
|
||||
add_test(function test_sendUnknownMessage() {
|
||||
try {
|
||||
KillSwitchMain.receiveMessage(uMessage);
|
||||
ok(false, "Should have failed");
|
||||
} catch (ex) {
|
||||
strictEqual(ex, Cr.NS_ERROR_ILLEGAL_VALUE);
|
||||
}
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
let fakeLibcUtils = {
|
||||
_props_: {},
|
||||
property_set: function(name, value) {
|
||||
dump("property_set('" + name + "', '" + value+ "' [" + (typeof value) + "]);\n");
|
||||
this._props_[name] = value;
|
||||
},
|
||||
property_get: function(name, defaultValue) {
|
||||
dump("property_get('" + name + "', '" + defaultValue+ "');\n");
|
||||
if (Object.keys(this._props_).indexOf(name) !== -1) {
|
||||
return this._props_[name];
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
add_test(function test_nolibcutils() {
|
||||
KillSwitchMain._libcutils = null;
|
||||
try {
|
||||
KillSwitchMain.checkLibcUtils();
|
||||
ok(false, "Should have failed");
|
||||
} catch (ex) {
|
||||
strictEqual(ex, Cr.NS_ERROR_NO_INTERFACE);
|
||||
}
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_install_fakelibcutils() {
|
||||
KillSwitchMain._libcutils = fakeLibcUtils;
|
||||
let rv = KillSwitchMain.checkLibcUtils();
|
||||
strictEqual(rv, true);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
install_common_tests();
|
||||
@@ -0,0 +1,42 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
|
||||
Cu.import("resource://gre/modules/systemlibs.js");
|
||||
return libcutils;
|
||||
});
|
||||
|
||||
// Trivial test just to make sure we have no syntax error
|
||||
add_test(function test_ksm_ok() {
|
||||
ok(KillSwitchMain, "KillSwitchMain object exists");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_has_libcutils() {
|
||||
let rv = KillSwitchMain.checkLibcUtils();
|
||||
strictEqual(rv, true);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_libcutils_works() {
|
||||
KillSwitchMain._libcutils.property_set("ro.moz.ks_test", "wesh");
|
||||
let rv_ks_get = KillSwitchMain._libcutils.property_get("ro.moz.ks_test");
|
||||
strictEqual(rv_ks_get, "wesh")
|
||||
let rv_sys_get = libcutils.property_get("ro.moz.ks_test")
|
||||
strictEqual(rv_sys_get, "wesh")
|
||||
|
||||
KillSwitchMain._libcutils.property_set("ro.moz.ks_test2", "123456789");
|
||||
rv_ks_get = KillSwitchMain._libcutils.property_get("ro.moz.ks_test2");
|
||||
strictEqual(rv_ks_get, "123456789")
|
||||
rv_sys_get = libcutils.property_get("ro.moz.ks_test2")
|
||||
strictEqual(rv_sys_get, "123456789")
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
install_common_tests();
|
||||
@@ -54,7 +54,8 @@ add_test(function test_print_properties() {
|
||||
"sys.usb.state": "diag,serial_smd,serial_tty,rmnet_bam,mass_storage,adb"
|
||||
};
|
||||
|
||||
let logMessages = LogParser.prettyPrintPropertiesArray(properties);
|
||||
let logMessagesRaw = LogParser.prettyPrintPropertiesArray(properties);
|
||||
let logMessages = new TextDecoder("utf-8").decode(logMessagesRaw);
|
||||
let logMessagesArray = logMessages.split("\n");
|
||||
|
||||
ok(logMessagesArray.length === 3, "There should be 3 lines in the log.");
|
||||
|
||||
@@ -15,9 +15,14 @@ const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/LogCapture.jsm");
|
||||
Cu.import("resource://gre/modules/LogShake.jsm");
|
||||
|
||||
// Force logshake to handle a device motion event with given components
|
||||
// Does not use SystemAppProxy because event needs special
|
||||
// accelerationIncludingGravity property
|
||||
const EVENTS_PER_SECOND = 6.25;
|
||||
const GRAVITY = 9.8;
|
||||
|
||||
/**
|
||||
* Force logshake to handle a device motion event with given components.
|
||||
* Does not use SystemAppProxy because event needs special
|
||||
* accelerationIncludingGravity property.
|
||||
*/
|
||||
function sendDeviceMotionEvent(x, y, z) {
|
||||
let event = {
|
||||
type: "devicemotion",
|
||||
@@ -30,8 +35,10 @@ function sendDeviceMotionEvent(x, y, z) {
|
||||
LogShake.handleEvent(event);
|
||||
}
|
||||
|
||||
// Send a screen change event directly, does not use SystemAppProxy due to race
|
||||
// conditions.
|
||||
/**
|
||||
* Send a screen change event directly, does not use SystemAppProxy due to race
|
||||
* conditions.
|
||||
*/
|
||||
function sendScreenChangeEvent(screenEnabled) {
|
||||
let event = {
|
||||
type: "screenchange",
|
||||
@@ -42,23 +49,41 @@ function sendScreenChangeEvent(screenEnabled) {
|
||||
LogShake.handleEvent(event);
|
||||
}
|
||||
|
||||
function debug(msg) {
|
||||
var timestamp = Date.now();
|
||||
dump("LogShake: " + timestamp + ": " + msg);
|
||||
/**
|
||||
* Mock the readLogFile function of LogCapture.
|
||||
* Used to detect whether LogShake activates.
|
||||
* @return {Array<String>} Locations that LogShake tries to read
|
||||
*/
|
||||
function mockReadLogFile() {
|
||||
let readLocations = [];
|
||||
|
||||
LogCapture.readLogFile = function(loc) {
|
||||
readLocations.push(loc);
|
||||
return null; // we don't want to provide invalid data to a parser
|
||||
};
|
||||
|
||||
// Allow inspection of readLocations by caller
|
||||
return readLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a series of events that corresponds to a shake
|
||||
*/
|
||||
function sendSustainedShake() {
|
||||
// Fire a series of devicemotion events that are of shake magnitude
|
||||
for (let i = 0; i < 2 * EVENTS_PER_SECOND; i++) {
|
||||
sendDeviceMotionEvent(0, 2 * GRAVITY, 2 * GRAVITY);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
add_test(function test_do_log_capture_after_shaking() {
|
||||
// Enable LogShake
|
||||
LogShake.init();
|
||||
|
||||
let readLocations = [];
|
||||
LogCapture.readLogFile = function(loc) {
|
||||
readLocations.push(loc);
|
||||
return null; // we don't want to provide invalid data to a parser
|
||||
};
|
||||
let readLocations = mockReadLogFile();
|
||||
|
||||
// Fire a devicemotion event that is of shake magnitude
|
||||
sendDeviceMotionEvent(9001, 9001, 9001);
|
||||
sendSustainedShake();
|
||||
|
||||
ok(readLocations.length > 0,
|
||||
"LogShake should attempt to read at least one log");
|
||||
@@ -71,36 +96,28 @@ add_test(function test_do_nothing_when_resting() {
|
||||
// Enable LogShake
|
||||
LogShake.init();
|
||||
|
||||
let readLocations = [];
|
||||
LogCapture.readLogFile = function(loc) {
|
||||
readLocations.push(loc);
|
||||
return null; // we don't want to provide invalid data to a parser
|
||||
};
|
||||
let readLocations = mockReadLogFile();
|
||||
|
||||
// Fire a devicemotion event that is relatively tiny
|
||||
sendDeviceMotionEvent(0, 9.8, 9.8);
|
||||
// Fire several devicemotion events that are relatively tiny
|
||||
for (let i = 0; i < 2 * EVENTS_PER_SECOND; i++) {
|
||||
sendDeviceMotionEvent(0, GRAVITY, GRAVITY);
|
||||
}
|
||||
|
||||
ok(readLocations.length === 0,
|
||||
"LogShake should not read any logs");
|
||||
|
||||
debug("test_do_nothing_when_resting: stop");
|
||||
LogShake.uninit();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_do_nothing_when_disabled() {
|
||||
debug("test_do_nothing_when_disabled: start");
|
||||
// Disable LogShake
|
||||
LogShake.uninit();
|
||||
|
||||
let readLocations = [];
|
||||
LogCapture.readLogFile = function(loc) {
|
||||
readLocations.push(loc);
|
||||
return null; // we don't want to provide invalid data to a parser
|
||||
};
|
||||
let readLocations = mockReadLogFile();
|
||||
|
||||
// Fire a devicemotion event that would normally be a shake
|
||||
sendDeviceMotionEvent(0, 9001, 9001);
|
||||
// Fire a series of events that would normally be a shake
|
||||
sendSustainedShake();
|
||||
|
||||
ok(readLocations.length === 0,
|
||||
"LogShake should not read any logs");
|
||||
@@ -112,18 +129,13 @@ add_test(function test_do_nothing_when_screen_off() {
|
||||
// Enable LogShake
|
||||
LogShake.init();
|
||||
|
||||
|
||||
// Send an event as if the screen has been turned off
|
||||
sendScreenChangeEvent(false);
|
||||
|
||||
let readLocations = [];
|
||||
LogCapture.readLogFile = function(loc) {
|
||||
readLocations.push(loc);
|
||||
return null; // we don't want to provide invalid data to a parser
|
||||
};
|
||||
let readLocations = mockReadLogFile();
|
||||
|
||||
// Fire a devicemotion event that would normally be a shake
|
||||
sendDeviceMotionEvent(0, 9001, 9001);
|
||||
// Fire a series of events that would normally be a shake
|
||||
sendSustainedShake();
|
||||
|
||||
ok(readLocations.length === 0,
|
||||
"LogShake should not read any logs");
|
||||
@@ -145,8 +157,8 @@ add_test(function test_do_log_capture_resilient_readLogFile() {
|
||||
throw new Error("Exception during readLogFile for: " + loc);
|
||||
};
|
||||
|
||||
// Fire a devicemotion event that is of shake magnitude
|
||||
sendDeviceMotionEvent(9001, 9001, 9001);
|
||||
// Fire a series of events that would normally be a shake
|
||||
sendSustainedShake();
|
||||
|
||||
ok(readLocations.length > 0,
|
||||
"LogShake should attempt to read at least one log");
|
||||
@@ -168,8 +180,8 @@ add_test(function test_do_log_capture_resilient_parseLog() {
|
||||
return null;
|
||||
};
|
||||
|
||||
// Fire a devicemotion event that is of shake magnitude
|
||||
sendDeviceMotionEvent(9001, 9001, 9001);
|
||||
// Fire a series of events that would normally be a shake
|
||||
sendSustainedShake();
|
||||
|
||||
ok(readLocations.length > 0,
|
||||
"LogShake should attempt to read at least one log");
|
||||
@@ -178,7 +190,29 @@ add_test(function test_do_log_capture_resilient_parseLog() {
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_do_nothing_when_dropped() {
|
||||
// Enable LogShake
|
||||
LogShake.init();
|
||||
|
||||
let readLocations = mockReadLogFile();
|
||||
|
||||
// We want a series of spikes to be ignored by LogShake. This roughly
|
||||
// corresponds to the compare_stairs_sock graph on bug #1101994
|
||||
|
||||
for (let i = 0; i < 10 * EVENTS_PER_SECOND; i++) {
|
||||
// Fire a devicemotion event that is at rest
|
||||
sendDeviceMotionEvent(0, 0, GRAVITY);
|
||||
// Fire a spike of motion
|
||||
sendDeviceMotionEvent(0, 2 * GRAVITY, 2 * GRAVITY);
|
||||
}
|
||||
|
||||
ok(readLocations.length === 0,
|
||||
"LogShake should not read any logs");
|
||||
|
||||
LogShake.uninit();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
debug("Starting");
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
* for Gonk-specific parts
|
||||
*/
|
||||
|
||||
/* jshint moz: true */
|
||||
/* global Components, LogCapture, LogShake, ok, add_test, run_next_test, dump */
|
||||
/* jshint moz: true, esnext: true */
|
||||
/* global Cu, LogCapture, LogShake, ok, add_test, run_next_test, dump,
|
||||
setup_logshake_mocks, OS, sdcard */
|
||||
/* exported run_test */
|
||||
|
||||
/* disable use strict warning */
|
||||
@@ -12,77 +13,14 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
|
||||
"@mozilla.org/telephony/volume-service;1",
|
||||
"nsIVolumeService");
|
||||
|
||||
let sdcard;
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
function run_test() {
|
||||
Cu.import("resource://gre/modules/LogShake.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
do_get_profile();
|
||||
debug("Starting");
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function debug(msg) {
|
||||
var timestamp = Date.now();
|
||||
dump("LogShake: " + timestamp + ": " + msg + "\n");
|
||||
}
|
||||
|
||||
add_test(function setup_fs() {
|
||||
OS.File.makeDir("/data/local/tmp/sdcard/", {from: "/data"}).then(function() {
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function setup_sdcard() {
|
||||
let volName = "sdcard";
|
||||
let mountPoint = "/data/local/tmp/sdcard";
|
||||
volumeService.createFakeVolume(volName, mountPoint);
|
||||
|
||||
let vol = volumeService.getVolumeByName(volName);
|
||||
ok(vol, "volume shouldn't be null");
|
||||
equal(volName, vol.name, "name");
|
||||
equal(Ci.nsIVolume.STATE_MOUNTED, vol.state, "state");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_ensure_sdcard() {
|
||||
sdcard = volumeService.getVolumeByName("sdcard").mountPoint;
|
||||
ok(sdcard, "Should have a valid sdcard mountpoint");
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_logShake_captureLogs_returns() {
|
||||
// Enable LogShake
|
||||
LogShake.init();
|
||||
|
||||
LogShake.captureLogs().then(logResults => {
|
||||
LogShake.uninit();
|
||||
|
||||
ok(logResults.logFilenames.length > 0, "Should have filenames");
|
||||
ok(logResults.logPrefix.length > 0, "Should have prefix");
|
||||
|
||||
run_next_test();
|
||||
},
|
||||
error => {
|
||||
LogShake.uninit();
|
||||
|
||||
ok(false, "Should not have received error: " + error);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
add_test(setup_logshake_mocks);
|
||||
|
||||
add_test(function test_logShake_captureLogs_writes() {
|
||||
// Enable LogShake
|
||||
@@ -93,7 +31,11 @@ add_test(function test_logShake_captureLogs_writes() {
|
||||
LogShake.captureLogs().then(logResults => {
|
||||
LogShake.uninit();
|
||||
|
||||
logResults.logFilenames.forEach(f => {
|
||||
ok(logResults.logFilenames.length > 0, "Should have filenames");
|
||||
ok(logResults.logPaths.length > 0, "Should have paths");
|
||||
ok(!logResults.compressed, "Should not be compressed");
|
||||
|
||||
logResults.logPaths.forEach(f => {
|
||||
let p = OS.Path.join(sdcard, f);
|
||||
ok(p, "Should have a valid result path: " + p);
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Test the log capturing capabilities of LogShake.jsm, checking
|
||||
* for Gonk-specific parts
|
||||
*/
|
||||
|
||||
/* jshint moz: true, esnext: true */
|
||||
/* global Cc, Ci, Cu, LogCapture, LogShake, ok, add_test, run_next_test, dump,
|
||||
setup_logshake_mocks, OS, sdcard, FileUtils */
|
||||
/* exported run_test */
|
||||
|
||||
/* disable use strict warning */
|
||||
/* jshint -W097 */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
function run_test() {
|
||||
Cu.import("resource://gre/modules/LogShake.jsm");
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(setup_logshake_mocks);
|
||||
|
||||
add_test(function test_logShake_captureLogs_writes_zip() {
|
||||
// Enable LogShake
|
||||
LogShake.init();
|
||||
|
||||
let expectedFiles = [];
|
||||
|
||||
LogShake.enableQAMode();
|
||||
|
||||
LogShake.captureLogs().then(logResults => {
|
||||
LogShake.uninit();
|
||||
|
||||
ok(logResults.logPaths.length === 1, "Should have zip path");
|
||||
ok(logResults.logFilenames.length >= 1, "Should have log filenames");
|
||||
ok(logResults.compressed, "Log files should be compressed");
|
||||
|
||||
let zipPath = OS.Path.join(sdcard, logResults.logPaths[0]);
|
||||
ok(zipPath, "Should have a valid archive path: " + zipPath);
|
||||
|
||||
let zipFile = new FileUtils.File(zipPath);
|
||||
ok(zipFile, "Should have a valid archive file: " + zipFile);
|
||||
|
||||
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
|
||||
.createInstance(Ci.nsIZipReader);
|
||||
zipReader.open(zipFile);
|
||||
|
||||
let logFilenamesSeen = {};
|
||||
|
||||
let zipEntries = zipReader.findEntries(null); // Find all entries
|
||||
while (zipEntries.hasMore()) {
|
||||
let entryName = zipEntries.getNext();
|
||||
let entry = zipReader.getEntry(entryName);
|
||||
logFilenamesSeen[entryName] = true;
|
||||
ok(!entry.isDirectory, "Archive entry " + entryName + " should be a file");
|
||||
}
|
||||
zipReader.close();
|
||||
|
||||
// TODO: Verify archive contents
|
||||
logResults.logFilenames.forEach(filename => {
|
||||
ok(logFilenamesSeen[filename], "File " + filename + " should be present in archive");
|
||||
});
|
||||
run_next_test();
|
||||
},
|
||||
error => {
|
||||
LogShake.uninit();
|
||||
|
||||
ok(false, "Should not have received error: " + error);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Test the log capturing capabilities of LogShake.jsm under conditions that
|
||||
* could cause races
|
||||
*/
|
||||
|
||||
/* jshint moz: true, esnext: true */
|
||||
/* global Cu, LogCapture, LogShake, ok, add_test, run_next_test, dump,
|
||||
XPCOMUtils, do_get_profile, OS, volumeService, Promise, equal,
|
||||
setup_logshake_mocks */
|
||||
/* exported run_test */
|
||||
|
||||
/* disable use strict warning */
|
||||
/* jshint -W097 */
|
||||
|
||||
"use strict";
|
||||
|
||||
function run_test() {
|
||||
Cu.import("resource://gre/modules/LogShake.jsm");
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(setup_logshake_mocks);
|
||||
|
||||
add_test(function test_logShake_captureLogs_waits_to_read() {
|
||||
// Enable LogShake
|
||||
LogShake.init();
|
||||
|
||||
// Save no logs synchronously (except properties)
|
||||
LogShake.LOGS_WITH_PARSERS = {};
|
||||
|
||||
LogShake.captureLogs().then(logResults => {
|
||||
LogShake.uninit();
|
||||
|
||||
ok(logResults.logFilenames.length > 0, "Should have filenames");
|
||||
ok(logResults.logPaths.length > 0, "Should have paths");
|
||||
ok(!logResults.compressed, "Should not be compressed");
|
||||
|
||||
// This assumes that the about:memory reading will only fail under abnormal
|
||||
// circumstances. It does not check for screenshot.png because
|
||||
// systemAppFrame is unavailable during xpcshell tests.
|
||||
let hasAboutMemory = false;
|
||||
|
||||
logResults.logFilenames.forEach(filename => {
|
||||
// Because the about:memory log's filename has the PID in it we can not
|
||||
// use simple equality but instead search for the "about_memory" part of
|
||||
// the filename which will look like logshake-about_memory-{PID}.json.gz
|
||||
if (filename.indexOf("about_memory") < 0) {
|
||||
return;
|
||||
}
|
||||
hasAboutMemory = true;
|
||||
});
|
||||
|
||||
ok(hasAboutMemory,
|
||||
"LogShake's asynchronous read of about:memory should have succeeded.");
|
||||
|
||||
run_next_test();
|
||||
},
|
||||
error => {
|
||||
LogShake.uninit();
|
||||
|
||||
ok(false, "Should not have received error: " + error);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
@@ -26,7 +26,27 @@ skip-if = toolkit != "gonk"
|
||||
[test_logshake.js]
|
||||
|
||||
[test_logshake_gonk.js]
|
||||
head = head_logshake_gonk.js
|
||||
# only run on b2g builds due to requiring b2g-specific log files to exist
|
||||
skip-if = ((toolkit != "gonk") || ((toolkit == "gonk") && (debug == true))) # bug 1125989: disabled because of race condition in OS.File.makeDir
|
||||
skip-if = (toolkit != "gonk")
|
||||
|
||||
[test_logshake_gonk_compression.js]
|
||||
head = head_logshake_gonk.js
|
||||
# only run on b2g builds due to requiring b2g-specific log files to exist
|
||||
skip-if = (toolkit != "gonk")
|
||||
|
||||
[test_logshake_readLog_gonk.js]
|
||||
head = head_logshake_gonk.js
|
||||
# only run on b2g builds due to requiring b2g-specific log files to exist
|
||||
skip-if = (toolkit != "gonk")
|
||||
|
||||
[test_aboutserviceworkers.js]
|
||||
|
||||
[test_killswitch.js]
|
||||
head = file_killswitch.js
|
||||
skip-if = (toolkit == "gonk")
|
||||
|
||||
[test_killswitch_gonk.js]
|
||||
head = file_killswitch.js
|
||||
# Bug 1193677: disable on B2G ICS Emulator for intermittent failures with IndexedDB
|
||||
skip-if = ((toolkit != "gonk") || (toolkit == "gonk" && debug))
|
||||
|
||||
@@ -942,6 +942,7 @@ bin/libfreebl_32int64_3.so
|
||||
@RESPATH@/components/B2GAppMigrator.js
|
||||
@RESPATH@/components/B2GPresentationDevicePrompt.js
|
||||
@RESPATH@/components/PresentationRequestUIGlue.js
|
||||
@RESPATH@/components/KillSwitch.js
|
||||
|
||||
#ifndef MOZ_WIDGET_GONK
|
||||
@RESPATH@/components/SimulatorScreen.js
|
||||
|
||||
@@ -66,6 +66,13 @@ function init(aEvent)
|
||||
|
||||
#ifdef MOZ_UPDATER
|
||||
gAppUpdater = new appUpdater();
|
||||
|
||||
let defaults = Services.prefs.getDefaultBranch("");
|
||||
let channelLabel = document.getElementById("currentChannel");
|
||||
let currentChannelText = document.getElementById("currentChannelText");
|
||||
channelLabel.value = UpdateUtils.UpdateChannel;
|
||||
if (/^release($|\-)/.test(channelLabel.value))
|
||||
currentChannelText.hidden = true;
|
||||
#endif
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
@@ -89,6 +96,9 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
|
||||
var gAppUpdater;
|
||||
|
||||
function onUnload(aEvent) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* it changes the sidebar's visibility.
|
||||
* - group this attribute must be set to "sidebar".
|
||||
*/
|
||||
let SidebarUI = {
|
||||
var SidebarUI = {
|
||||
browser: null,
|
||||
|
||||
_box: null,
|
||||
|
||||
@@ -34,8 +34,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Log",
|
||||
"resource://gre/modules/Log.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
||||
"resource://gre/modules/AppConstants.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
"resource://gre/modules/UpdateChannel.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
|
||||
"@mozilla.org/browser/favicon-service;1",
|
||||
"mozIAsyncFavicons");
|
||||
@@ -2749,7 +2749,7 @@ var BrowserOnClick = {
|
||||
version: 1,
|
||||
build: gAppInfo.appBuildID,
|
||||
product: gAppInfo.name,
|
||||
channel: UpdateChannel.get()
|
||||
channel: UpdateUtils.UpdateChannel
|
||||
}
|
||||
|
||||
let reportURL = Services.prefs.getCharPref("security.ssl.errorReporting.url");
|
||||
|
||||
@@ -239,7 +239,7 @@ Cc["@mozilla.org/eventlistenerservice;1"]
|
||||
.getService(Ci.nsIEventListenerService)
|
||||
.addSystemEventListener(global, "contextmenu", handleContentContextMenu, false);
|
||||
|
||||
let AboutNetErrorListener = {
|
||||
var AboutNetErrorListener = {
|
||||
init: function(chromeGlobal) {
|
||||
chromeGlobal.addEventListener('AboutNetErrorLoad', this, false, true);
|
||||
chromeGlobal.addEventListener('AboutNetErrorSetAutomatic', this, false, true);
|
||||
@@ -354,7 +354,8 @@ let AboutNetErrorListener = {
|
||||
|
||||
AboutNetErrorListener.init(this);
|
||||
|
||||
let ClickEventHandler = {
|
||||
|
||||
var ClickEventHandler = {
|
||||
init: function init() {
|
||||
Cc["@mozilla.org/eventlistenerservice;1"]
|
||||
.getService(Ci.nsIEventListenerService)
|
||||
|
||||
@@ -140,7 +140,7 @@ var gDrag = {
|
||||
// drag image with its default opacity.
|
||||
let dragElement = document.createElementNS(HTML_NAMESPACE, "div");
|
||||
dragElement.classList.add("newtab-drag");
|
||||
let scrollbox = document.getElementById("newtab-scrollbox");
|
||||
let scrollbox = document.getElementById("newtab-vertical-margin");
|
||||
scrollbox.appendChild(dragElement);
|
||||
dt.setDragImage(dragElement, 0, 0);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#endif
|
||||
|
||||
let gDragDataHelper = {
|
||||
var gDragDataHelper = {
|
||||
get mimeType() {
|
||||
return "text/x-moz-url";
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ const DELAY_REARRANGE_MS = 100;
|
||||
/**
|
||||
* This singleton implements site dropping functionality.
|
||||
*/
|
||||
let gDrop = {
|
||||
var gDrop = {
|
||||
/**
|
||||
* The last drop target.
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* indicate the transformation that results from dropping a cell at a certain
|
||||
* position.
|
||||
*/
|
||||
let gDropPreview = {
|
||||
var gDropPreview = {
|
||||
/**
|
||||
* Rearranges the sites currently contained in the grid when a site would be
|
||||
* dropped onto the given cell.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* the default DnD target detection relies on the cursor's position. We want
|
||||
* to pick a drop target based on the dragged site's position.
|
||||
*/
|
||||
let gDropTargetShim = {
|
||||
var gDropTargetShim = {
|
||||
/**
|
||||
* Cache for the position of all cells, cleaned after drag finished.
|
||||
*/
|
||||
|
||||
@@ -4,10 +4,17 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Define various fixed dimensions
|
||||
*/
|
||||
const GRID_BOTTOM_EXTRA = 7; // title's line-height extends 7px past the margin
|
||||
const GRID_WIDTH_EXTRA = 1; // provide 1px buffer to allow for rounding error
|
||||
const SPONSORED_TAG_BUFFER = 2; // 2px buffer to clip off top of sponsored tag
|
||||
|
||||
/**
|
||||
* This singleton represents the grid that contains all sites.
|
||||
*/
|
||||
let gGrid = {
|
||||
var gGrid = {
|
||||
/**
|
||||
* The DOM node of the grid.
|
||||
*/
|
||||
|
||||
@@ -2,26 +2,36 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
input[type=button] {
|
||||
cursor: pointer;
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* SCROLLBOX */
|
||||
#newtab-scrollbox {
|
||||
body {
|
||||
font: message-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: #F9F9F9;
|
||||
display: -moz-box;
|
||||
position: relative;
|
||||
-moz-box-flex: 1;
|
||||
-moz-user-focus: normal;
|
||||
}
|
||||
|
||||
#newtab-scrollbox:not([page-disabled]) {
|
||||
overflow: auto;
|
||||
input {
|
||||
font: message-box !important;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
input[type=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* UNDO */
|
||||
#newtab-undo-container {
|
||||
transition: opacity 100ms ease-out;
|
||||
display: -moz-box;
|
||||
-moz-box-align: center;
|
||||
-moz-box-pack: center;
|
||||
}
|
||||
@@ -38,7 +48,7 @@ input[type=button] {
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
#newtab-toggle:-moz-locale-dir(rtl) {
|
||||
#newtab-toggle:-moz-dir(rtl) {
|
||||
left: 12px;
|
||||
right: auto;
|
||||
}
|
||||
@@ -117,7 +127,9 @@ input[type=button] {
|
||||
/* CELLS */
|
||||
.newtab-cell {
|
||||
display: -moz-box;
|
||||
-moz-box-flex: 1;
|
||||
height: 210px;
|
||||
margin: 20px 10px 35px;
|
||||
width: 290px;
|
||||
}
|
||||
|
||||
/* SITES */
|
||||
@@ -148,18 +160,127 @@ input[type=button] {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.newtab-thumbnail {
|
||||
opacity: .8;
|
||||
transition: opacity 100ms ease-out;
|
||||
}
|
||||
|
||||
.newtab-thumbnail[dragged],
|
||||
.newtab-link:-moz-focusring > .newtab-thumbnail,
|
||||
.newtab-site:hover > .newtab-link > .newtab-thumbnail {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* TITLES */
|
||||
.newtab-sponsored,
|
||||
.newtab-title,
|
||||
.newtab-suggested {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.newtab-sponsored,
|
||||
.newtab-title {
|
||||
bottom: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 13px;
|
||||
line-height: 30px;
|
||||
vertical-align: middle;
|
||||
background-color: #F2F2F2;
|
||||
}
|
||||
|
||||
.newtab-suggested {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
height: 17px;
|
||||
line-height: 17px;
|
||||
margin-bottom: -1px;
|
||||
padding: 2px 8px;
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
top: 215px;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.newtab-suggested-bounds {
|
||||
max-height: 34px; /* 34 / 17 = 2 lines maximum */
|
||||
}
|
||||
|
||||
.newtab-title {
|
||||
left: 0;
|
||||
padding: 0 4px;
|
||||
border: 1px solid #FFFFFF;
|
||||
border-radius: 0px 0px 8px 8px;
|
||||
}
|
||||
|
||||
.newtab-sponsored {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #E2E2E2;
|
||||
border-radius: 3px;
|
||||
color: #4A4A4A;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
font-family: Arial;
|
||||
font-size: 9px;
|
||||
height: 17px;
|
||||
left: 0;
|
||||
line-height: 6px;
|
||||
padding: 4px;
|
||||
right: auto;
|
||||
top: -15px;
|
||||
}
|
||||
|
||||
.newtab-site[suggested=true] > .newtab-sponsored {
|
||||
background-color: #E2E2E2;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.newtab-site > .newtab-sponsored:-moz-any(:hover, [active]) {
|
||||
background-color: #4A90E2;
|
||||
border: 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.newtab-site > .newtab-sponsored[active] {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.newtab-sponsored:-moz-dir(rtl) {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.newtab-site:-moz-any([type=enhanced], [type=sponsored], [suggested]) .newtab-sponsored {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.newtab-site[suggested] .newtab-suggested {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.sponsored-explain,
|
||||
.sponsored-explain a,
|
||||
.suggested-explain,
|
||||
.suggested-explain a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sponsored-explain,
|
||||
.suggested-explain {
|
||||
background-color: rgba(51, 51, 51, 0.95);
|
||||
bottom: 30px;
|
||||
line-height: 20px;
|
||||
padding: 15px 10px;
|
||||
position: absolute;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.sponsored-explain input,
|
||||
.suggested-explain input {
|
||||
background-size: 18px;
|
||||
height: 18px;
|
||||
opacity: 1;
|
||||
pointer-events: none;
|
||||
position: static;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
|
||||
.newtab-title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -193,13 +314,13 @@ input[type=button] {
|
||||
}
|
||||
}
|
||||
|
||||
.newtab-control-pin:-moz-locale-dir(ltr),
|
||||
.newtab-control-block:-moz-locale-dir(rtl) {
|
||||
.newtab-control-pin:-moz-dir(ltr),
|
||||
.newtab-control-block:-moz-dir(rtl) {
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
.newtab-control-block:-moz-locale-dir(ltr),
|
||||
.newtab-control-pin:-moz-locale-dir(rtl) {
|
||||
.newtab-control-block:-moz-dir(ltr),
|
||||
.newtab-control-pin:-moz-dir(rtl) {
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
@@ -226,8 +347,8 @@ input[type=button] {
|
||||
#newtab-search-container {
|
||||
display: -moz-box;
|
||||
position: relative;
|
||||
-moz-box-align: center;
|
||||
-moz-box-pack: center;
|
||||
margin: 40px 0 15px;
|
||||
}
|
||||
|
||||
#newtab-search-container[page-disabled] {
|
||||
@@ -330,6 +451,7 @@ input[type=button] {
|
||||
}
|
||||
|
||||
#newtab-search-text:focus + #newtab-search-submit,
|
||||
#newtab-search-text[keepfocus] + #newtab-search-submit,
|
||||
#newtab-search-text[autofocus] + #newtab-search-submit {
|
||||
background-image: linear-gradient(#4cb1ff, #1793e5);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
|
||||
|
||||
@@ -34,15 +34,23 @@ XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
|
||||
createBundle("chrome://browser/locale/newTab.properties");
|
||||
});
|
||||
|
||||
function newTabString(name) gStringBundle.GetStringFromName('newtab.' + name);
|
||||
function newTabString(name, args) {
|
||||
let stringName = "newtab." + name;
|
||||
if (!args) {
|
||||
return gStringBundle.GetStringFromName(stringName);
|
||||
}
|
||||
return gStringBundle.formatStringFromName(stringName, args, args.length);
|
||||
}
|
||||
|
||||
function inPrivateBrowsingMode() {
|
||||
return PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
return PrivateBrowsingUtils.isContentWindowPrivate(window);
|
||||
}
|
||||
|
||||
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
||||
const XUL_NAMESPACE = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
const TILES_EXPLAIN_LINK = "https://support.mozilla.org/kb/how-do-tiles-work-firefox";
|
||||
|
||||
#include transformations.js
|
||||
#include page.js
|
||||
#include grid.js
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
|
||||
%newTabDTD;
|
||||
<!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd">
|
||||
%searchBarDTD;
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&newtab.pageTitle;</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/" />
|
||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/searchSuggestionUI.css" />
|
||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/newtab/newTab.css" />
|
||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/newtab/newTab.css" />
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<div id="newtab-customize-overlay"></div>
|
||||
|
||||
<div id="newtab-scrollbox">
|
||||
|
||||
<div id="newtab-vertical-margin">
|
||||
<div id="newtab-margin-top"/>
|
||||
<div id="newtab-margin-undo-container">
|
||||
<div id="newtab-undo-container" undo-disabled="true">
|
||||
<label id="newtab-undo-label">&newtab.undo.removedLabel;</label>
|
||||
<button id="newtab-undo-button" tabindex="-1"
|
||||
class="newtab-undo-button">&newtab.undo.undoButton;</button>
|
||||
<button id="newtab-undo-restore-button" tabindex="-1"
|
||||
class="newtab-undo-button">&newtab.undo.restoreButton;</button>
|
||||
<button id="newtab-undo-close-button" tabindex="-1" title="&newtab.undo.closeTooltip;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="newtab-search-container">
|
||||
<form id="newtab-search-form" name="searchForm">
|
||||
<div id="newtab-search-logo"/>
|
||||
<input type="text" name="q" value="" id="newtab-search-text"
|
||||
maxlength="256" dir="auto"/>
|
||||
<input id="newtab-search-submit" type="submit"
|
||||
value="&searchEndCap.label;"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="newtab-horizontal-margin">
|
||||
<div class="newtab-side-margin"/>
|
||||
|
||||
<div id="newtab-grid">
|
||||
</div>
|
||||
|
||||
<div class="newtab-side-margin"/>
|
||||
</div>
|
||||
|
||||
<div id="newtab-margin-bottom"/>
|
||||
</div>
|
||||
<input id="newtab-toggle" type="button"/>
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript;version=1.8" src="chrome://browser/content/searchSuggestionUI.js"/>
|
||||
<script type="text/javascript;version=1.8" src="chrome://browser/content/newtab/newTab.js"/>
|
||||
</html>
|
||||
@@ -1,79 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/searchSuggestionUI.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/newtab/newTab.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/newtab/newTab.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
|
||||
%newTabDTD;
|
||||
<!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd">
|
||||
%searchBarDTD;
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
]>
|
||||
|
||||
<xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="&newtab.pageTitle;">
|
||||
|
||||
<xul:panel id="newtab-search-panel" orient="vertical" type="arrow"
|
||||
noautohide="true">
|
||||
<xul:hbox id="newtab-search-manage" class="newtab-search-panel-engine">
|
||||
<xul:label>&changeSearchSettings.button;</xul:label>
|
||||
</xul:hbox>
|
||||
</xul:panel>
|
||||
|
||||
<div id="newtab-scrollbox">
|
||||
|
||||
<div id="newtab-vertical-margin">
|
||||
<div id="newtab-margin-top">
|
||||
<div id="newtab-undo-container" undo-disabled="true">
|
||||
<xul:label id="newtab-undo-label"
|
||||
value="&newtab.undo.removedLabel;" />
|
||||
<xul:button id="newtab-undo-button" tabindex="-1"
|
||||
label="&newtab.undo.undoButton;"
|
||||
class="newtab-undo-button" />
|
||||
<xul:button id="newtab-undo-restore-button" tabindex="-1"
|
||||
label="&newtab.undo.restoreButton;"
|
||||
class="newtab-undo-button" />
|
||||
<xul:toolbarbutton id="newtab-undo-close-button" tabindex="-1"
|
||||
class="close-icon"
|
||||
tooltiptext="&newtab.undo.closeTooltip;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="newtab-search-container">
|
||||
<form id="newtab-search-form" name="searchForm">
|
||||
<div id="newtab-search-logo"/>
|
||||
<input type="text" name="q" value="" id="newtab-search-text"
|
||||
maxlength="256" dir="auto"/>
|
||||
<input id="newtab-search-submit" type="submit"
|
||||
value="&searchEndCap.label;"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="newtab-horizontal-margin">
|
||||
<div class="newtab-side-margin"/>
|
||||
|
||||
<div id="newtab-grid">
|
||||
</div>
|
||||
|
||||
<div class="newtab-side-margin"/>
|
||||
</div>
|
||||
|
||||
<div id="newtab-margin-bottom"/>
|
||||
</div>
|
||||
<input id="newtab-toggle" type="button"/>
|
||||
</div>
|
||||
|
||||
<xul:script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/newtab/newTab.js"/>
|
||||
<xul:script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/searchSuggestionUI.js"/>
|
||||
</xul:window>
|
||||
@@ -8,7 +8,7 @@
|
||||
* This singleton represents the whole 'New Tab Page' and takes care of
|
||||
* initializing all its components.
|
||||
*/
|
||||
let gPage = {
|
||||
var gPage = {
|
||||
/**
|
||||
* Initializes the page.
|
||||
*/
|
||||
@@ -100,7 +100,7 @@ let gPage = {
|
||||
*/
|
||||
_updateAttributes: function Page_updateAttributes(aValue) {
|
||||
// Set the nodes' states.
|
||||
let nodeSelector = "#newtab-scrollbox, #newtab-toggle, #newtab-grid, #newtab-search-container";
|
||||
let nodeSelector = "#newtab-grid, #newtab-search-container";
|
||||
for (let node of document.querySelectorAll(nodeSelector)) {
|
||||
if (aValue)
|
||||
node.removeAttribute("page-disabled");
|
||||
@@ -111,7 +111,7 @@ let gPage = {
|
||||
// Enables/disables the control and link elements.
|
||||
let inputSelector = ".newtab-control, .newtab-link";
|
||||
for (let input of document.querySelectorAll(inputSelector)) {
|
||||
if (aValue)
|
||||
if (aValue)
|
||||
input.removeAttribute("tabindex");
|
||||
else
|
||||
input.setAttribute("tabindex", "-1");
|
||||
|
||||
@@ -37,7 +37,7 @@ Site.prototype = {
|
||||
/**
|
||||
* The title of the site's link.
|
||||
*/
|
||||
get title() { return this.link.title; },
|
||||
get title() { return this.link.title || this.link.url; },
|
||||
|
||||
/**
|
||||
* The site's parent cell.
|
||||
@@ -50,13 +50,19 @@ Site.prototype = {
|
||||
/**
|
||||
* Pins the site on its current or a given index.
|
||||
* @param aIndex The pinned index (optional).
|
||||
* @return true if link changed type after pin
|
||||
*/
|
||||
pin: function Site_pin(aIndex) {
|
||||
if (typeof aIndex == "undefined")
|
||||
aIndex = this.cell.index;
|
||||
|
||||
this._updateAttributes(true);
|
||||
gPinnedLinks.pin(this._link, aIndex);
|
||||
let changed = gPinnedLinks.pin(this._link, aIndex);
|
||||
if (changed) {
|
||||
// render site again to remove suggested/sponsored tags
|
||||
this._render();
|
||||
}
|
||||
return changed;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -108,14 +114,34 @@ Site.prototype = {
|
||||
let control = this._querySelector(".newtab-control-pin");
|
||||
|
||||
if (aPinned) {
|
||||
control.setAttribute("pinned", true);
|
||||
this.node.setAttribute("pinned", true);
|
||||
control.setAttribute("title", newTabString("unpin"));
|
||||
} else {
|
||||
control.removeAttribute("pinned");
|
||||
this.node.removeAttribute("pinned");
|
||||
control.setAttribute("title", newTabString("pin"));
|
||||
}
|
||||
},
|
||||
|
||||
_newTabString: function(str, substrArr) {
|
||||
let regExp = /%[0-9]\$S/g;
|
||||
let matches;
|
||||
while (matches = regExp.exec(str)) {
|
||||
let match = matches[0];
|
||||
let index = match.charAt(1); // Get the digit in the regExp.
|
||||
str = str.replace(match, substrArr[index - 1]);
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
_getSuggestedTileExplanation: function() {
|
||||
let targetedName = `<strong> ${this.link.targetedName} </strong>`;
|
||||
let targetedSite = `<strong> ${this.link.targetedSite} </strong>`;
|
||||
if (this.link.explanation) {
|
||||
return this._newTabString(this.link.explanation, [targetedName, targetedSite]);
|
||||
}
|
||||
return newTabString("suggested.button", [targetedName]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks for and modifies link at campaign end time
|
||||
*/
|
||||
@@ -144,20 +170,45 @@ Site.prototype = {
|
||||
// setup display variables
|
||||
let enhanced = gAllPages.enhanced && DirectoryLinksProvider.getEnhancedLink(this.link);
|
||||
let url = this.url;
|
||||
let title = this.title || url;
|
||||
let tooltip = (title == url ? title : title + "\n" + url);
|
||||
let title = enhanced && enhanced.title ? enhanced.title :
|
||||
this.link.type == "history" ? this.link.baseDomain :
|
||||
this.title;
|
||||
let tooltip = (this.title == url ? this.title : this.title + "\n" + url);
|
||||
|
||||
let link = this._querySelector(".newtab-link");
|
||||
link.setAttribute("title", tooltip);
|
||||
link.setAttribute("href", url);
|
||||
this._querySelector(".newtab-title").textContent = title;
|
||||
this.node.setAttribute("type", this.link.type);
|
||||
|
||||
let titleNode = this._querySelector(".newtab-title");
|
||||
titleNode.textContent = title;
|
||||
if (this.link.titleBgColor) {
|
||||
titleNode.style.backgroundColor = this.link.titleBgColor;
|
||||
}
|
||||
|
||||
// remove "suggested" attribute to avoid showing "suggested" tag
|
||||
// after site was pinned or dropped
|
||||
this.node.removeAttribute("suggested");
|
||||
|
||||
if (this.link.targetedSite) {
|
||||
if (this.node.getAttribute("type") != "sponsored") {
|
||||
this._querySelector(".newtab-sponsored").textContent =
|
||||
newTabString("suggested.tag");
|
||||
}
|
||||
|
||||
this.node.setAttribute("suggested", true);
|
||||
let explanation = this._getSuggestedTileExplanation();
|
||||
this._querySelector(".newtab-suggested").innerHTML =
|
||||
`<div class='newtab-suggested-bounds'> ${explanation} </div>`;
|
||||
}
|
||||
|
||||
if (this.isPinned())
|
||||
this._updateAttributes(true);
|
||||
|
||||
let thumbnailURL = PageThumbs.getThumbnailURL(this.url);
|
||||
let thumbnail = this._querySelector(".newtab-thumbnail");
|
||||
thumbnail.style.backgroundImage = "url(" + thumbnailURL + ")";
|
||||
// Capture the page if the thumbnail is missing, which will cause page.js
|
||||
// to be notified and call our refreshThumbnail() method.
|
||||
this.captureIfMissing();
|
||||
// but still display whatever thumbnail might be available now.
|
||||
this.refreshThumbnail();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -230,9 +281,12 @@ Site.prototype = {
|
||||
this._node.addEventListener("dragend", this, false);
|
||||
this._node.addEventListener("mouseover", this, false);
|
||||
|
||||
let controls = this.node.querySelectorAll(".newtab-control");
|
||||
for (let i = 0; i < controls.length; i++)
|
||||
controls[i].addEventListener("click", this, false);
|
||||
// Specially treat the sponsored icon & suggested explanation
|
||||
// text to prevent regular hover effects
|
||||
let sponsored = this._querySelector(".newtab-sponsored");
|
||||
let suggested = this._querySelector(".newtab-suggested");
|
||||
this._ignoreHoverEvents(sponsored);
|
||||
this._ignoreHoverEvents(suggested);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -244,6 +298,102 @@ Site.prototype = {
|
||||
sc.speculativeConnect(uri, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Record interaction with site using telemetry.
|
||||
*/
|
||||
_recordSiteClicked: function Site_recordSiteClicked(aIndex) {
|
||||
if (Services.prefs.prefHasUserValue("browser.newtabpage.rows") ||
|
||||
Services.prefs.prefHasUserValue("browser.newtabpage.columns") ||
|
||||
aIndex > 8) {
|
||||
// We only want to get indices for the default configuration, everything
|
||||
// else goes in the same bucket.
|
||||
aIndex = 9;
|
||||
}
|
||||
Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED")
|
||||
.add(aIndex);
|
||||
},
|
||||
|
||||
_toggleLegalText: function(buttonClass, explanationTextClass) {
|
||||
let button = this._querySelector(buttonClass);
|
||||
if (button.hasAttribute("active")) {
|
||||
let explain = this._querySelector(explanationTextClass);
|
||||
explain.parentNode.removeChild(explain);
|
||||
|
||||
button.removeAttribute("active");
|
||||
}
|
||||
else {
|
||||
let explain = document.createElementNS(HTML_NAMESPACE, "div");
|
||||
explain.className = explanationTextClass.slice(1); // Slice off the first character, '.'
|
||||
this.node.appendChild(explain);
|
||||
|
||||
let link = '<a href="' + TILES_EXPLAIN_LINK + '">' +
|
||||
newTabString("learn.link") + "</a>";
|
||||
let type = (this.node.getAttribute("suggested") && this.node.getAttribute("type") == "affiliate") ?
|
||||
"suggested" : this.node.getAttribute("type");
|
||||
let icon = '<input type="button" class="newtab-control newtab-' +
|
||||
(type == "enhanced" ? "customize" : "control-block") + '"/>';
|
||||
explain.innerHTML = newTabString(type + (type == "sponsored" ? ".explain2" : ".explain"), [icon, link]);
|
||||
|
||||
button.setAttribute("active", "true");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles site click events.
|
||||
*/
|
||||
onClick: function Site_onClick(aEvent) {
|
||||
let action;
|
||||
let pinned = this.isPinned();
|
||||
let tileIndex = this.cell.index;
|
||||
let {button, target} = aEvent;
|
||||
|
||||
// Handle tile/thumbnail link click
|
||||
if (target.classList.contains("newtab-link") ||
|
||||
target.parentElement.classList.contains("newtab-link")) {
|
||||
// Record for primary and middle clicks
|
||||
if (button == 0 || button == 1) {
|
||||
this._recordSiteClicked(tileIndex);
|
||||
action = "click";
|
||||
}
|
||||
}
|
||||
// Handle sponsored explanation link click
|
||||
else if (target.parentElement.classList.contains("sponsored-explain")) {
|
||||
action = "sponsored_link";
|
||||
}
|
||||
else if (target.parentElement.classList.contains("suggested-explain")) {
|
||||
action = "suggested_link";
|
||||
}
|
||||
// Only handle primary clicks for the remaining targets
|
||||
else if (button == 0) {
|
||||
aEvent.preventDefault();
|
||||
if (target.classList.contains("newtab-control-block")) {
|
||||
this.block();
|
||||
action = "block";
|
||||
}
|
||||
else if (target.classList.contains("sponsored-explain") ||
|
||||
target.classList.contains("newtab-sponsored")) {
|
||||
this._toggleLegalText(".newtab-sponsored", ".sponsored-explain");
|
||||
action = "sponsored";
|
||||
}
|
||||
else if (pinned && target.classList.contains("newtab-control-pin")) {
|
||||
this.unpin();
|
||||
action = "unpin";
|
||||
}
|
||||
else if (!pinned && target.classList.contains("newtab-control-pin")) {
|
||||
if (this.pin()) {
|
||||
// suggested link has changed - update rest of the pages
|
||||
gAllPages.update(gPage);
|
||||
}
|
||||
action = "pin";
|
||||
}
|
||||
}
|
||||
|
||||
// Report all link click actions
|
||||
if (action) {
|
||||
DirectoryLinksProvider.reportSitesAction(gGrid.sites, action, tileIndex);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles all site events.
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* in the DOM and by showing or hiding the node. It additionally provides
|
||||
* convenience methods to work with a site's DOM node.
|
||||
*/
|
||||
let gTransformation = {
|
||||
var gTransformation = {
|
||||
/**
|
||||
* Returns the width of the left and top border of a cell. We need to take it
|
||||
* into account when measuring and comparing site and cell positions.
|
||||
@@ -103,7 +103,7 @@ let gTransformation = {
|
||||
|
||||
let style = aSite.node.style;
|
||||
let comp = getComputedStyle(aSite.node, null);
|
||||
style.width = comp.getPropertyValue("width")
|
||||
style.width = comp.getPropertyValue("width");
|
||||
style.height = comp.getPropertyValue("height");
|
||||
|
||||
aSite.node.setAttribute("frozen", "true");
|
||||
@@ -156,7 +156,7 @@ let gTransformation = {
|
||||
finish();
|
||||
} else {
|
||||
this.setSitePosition(aSite, targetPosition);
|
||||
this._whenTransitionEnded(aSite.node, finish);
|
||||
this._whenTransitionEnded(aSite.node, ["left", "top"], finish);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -179,38 +179,42 @@ let gTransformation = {
|
||||
if (!aSite || aSite == gDrag.draggedSite)
|
||||
return;
|
||||
|
||||
let deferred = Promise.defer();
|
||||
batch.push(deferred.promise);
|
||||
let cb = function () deferred.resolve();
|
||||
|
||||
if (!cells[aIndex])
|
||||
// The site disappeared from the grid, hide it.
|
||||
this.hideSite(aSite, cb);
|
||||
else if (this._getNodeOpacity(aSite.node) != 1)
|
||||
// The site disappeared before but is now back, show it.
|
||||
this.showSite(aSite, cb);
|
||||
else
|
||||
// The site's position has changed, move it around.
|
||||
this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: cb});
|
||||
batch.push(new Promise(resolve => {
|
||||
if (!cells[aIndex]) {
|
||||
// The site disappeared from the grid, hide it.
|
||||
this.hideSite(aSite, resolve);
|
||||
} else if (this._getNodeOpacity(aSite.node) != 1) {
|
||||
// The site disappeared before but is now back, show it.
|
||||
this.showSite(aSite, resolve);
|
||||
} else {
|
||||
// The site's position has changed, move it around.
|
||||
this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: resolve});
|
||||
}
|
||||
}));
|
||||
}, this);
|
||||
|
||||
let wait = Promise.promised(function () callback && callback());
|
||||
wait.apply(null, batch);
|
||||
if (callback) {
|
||||
Promise.all(batch).then(callback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listens for the 'transitionend' event on a given node and calls the given
|
||||
* callback.
|
||||
* @param aNode The node that is transitioned.
|
||||
* @param aProperties The properties we'll wait to be transitioned.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_whenTransitionEnded:
|
||||
function Transformation_whenTransitionEnded(aNode, aCallback) {
|
||||
function Transformation_whenTransitionEnded(aNode, aProperties, aCallback) {
|
||||
|
||||
aNode.addEventListener("transitionend", function onEnd() {
|
||||
aNode.removeEventListener("transitionend", onEnd, false);
|
||||
aCallback();
|
||||
}, false);
|
||||
let props = new Set(aProperties);
|
||||
aNode.addEventListener("transitionend", function onEnd(e) {
|
||||
if (props.has(e.propertyName)) {
|
||||
aNode.removeEventListener("transitionend", onEnd);
|
||||
aCallback();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -236,8 +240,9 @@ let gTransformation = {
|
||||
if (aCallback)
|
||||
aCallback();
|
||||
} else {
|
||||
if (aCallback)
|
||||
this._whenTransitionEnded(aNode, aCallback);
|
||||
if (aCallback) {
|
||||
this._whenTransitionEnded(aNode, ["opacity"], aCallback);
|
||||
}
|
||||
|
||||
aNode.style.opacity = aOpacity;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* Dialog allowing to undo the removal of single site or to completely restore
|
||||
* the grid's original state.
|
||||
*/
|
||||
let gUndoDialog = {
|
||||
var gUndoDialog = {
|
||||
/**
|
||||
* The undo dialog's timeout in miliseconds.
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* This singleton provides functionality to update the current grid to a new
|
||||
* set of pinned and blocked sites. It adds, moves and removes sites.
|
||||
*/
|
||||
let gUpdater = {
|
||||
var gUpdater = {
|
||||
/**
|
||||
* Updates the current grid according to its pinned and blocked sites.
|
||||
* This removes old, moves existing and creates new sites to fill gaps.
|
||||
|
||||
@@ -68,6 +68,12 @@
|
||||
<richlistbox id="login-fill-list"/>
|
||||
</vbox>
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
<popupnotification id="enable-e10s-notification" hidden="true">
|
||||
<popupnotificationcontent orient="vertical"/>
|
||||
</popupnotification>
|
||||
#endif
|
||||
|
||||
<popupnotification id="addon-progress-notification" hidden="true">
|
||||
<popupnotificationcontent orient="vertical">
|
||||
<progressmeter id="addon-progress-notification-progressmeter"/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
@@ -10,7 +11,7 @@ const appStartup = Services.startup;
|
||||
|
||||
Cu.import("resource://gre/modules/ResetProfile.jsm");
|
||||
|
||||
let defaultToReset = false;
|
||||
var defaultToReset = false;
|
||||
|
||||
function restartApp() {
|
||||
appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
@@ -276,7 +276,7 @@ var gSanitizePromptDialog = {
|
||||
this.showItemList();
|
||||
else
|
||||
this.hideItemList();
|
||||
}
|
||||
},
|
||||
|
||||
#ifdef CRH_DIALOG_TREE_VIEW
|
||||
// A duration value; used in the same context as Sanitizer.TIMESPAN_HOUR,
|
||||
|
||||
@@ -98,7 +98,7 @@ addMessageListener("SecondScreen:tab-mirror", function(message) {
|
||||
}
|
||||
});
|
||||
|
||||
let AboutHomeListener = {
|
||||
var AboutHomeListener = {
|
||||
init: function(chromeGlobal) {
|
||||
chromeGlobal.addEventListener('AboutHomeLoad', this, false, true);
|
||||
},
|
||||
@@ -265,7 +265,7 @@ let AboutHomeListener = {
|
||||
};
|
||||
AboutHomeListener.init(this);
|
||||
|
||||
let AboutPrivateBrowsingListener = {
|
||||
var AboutPrivateBrowsingListener = {
|
||||
init(chromeGlobal) {
|
||||
chromeGlobal.addEventListener("AboutPrivateBrowsingOpenWindow", this,
|
||||
false, true);
|
||||
@@ -288,7 +288,7 @@ let AboutPrivateBrowsingListener = {
|
||||
};
|
||||
AboutPrivateBrowsingListener.init(this);
|
||||
|
||||
let AboutReaderListener = {
|
||||
var AboutReaderListener = {
|
||||
|
||||
_articlePromise: null,
|
||||
|
||||
@@ -403,7 +403,7 @@ let AboutReaderListener = {
|
||||
AboutReaderListener.init();
|
||||
|
||||
|
||||
let ContentSearchMediator = {
|
||||
var ContentSearchMediator = {
|
||||
|
||||
whitelist: new Set([
|
||||
"about:home",
|
||||
@@ -458,7 +458,7 @@ let ContentSearchMediator = {
|
||||
};
|
||||
ContentSearchMediator.init(this);
|
||||
|
||||
let PageStyleHandler = {
|
||||
var PageStyleHandler = {
|
||||
init: function() {
|
||||
addMessageListener("PageStyle:Switch", this);
|
||||
addMessageListener("PageStyle:Disable", this);
|
||||
@@ -587,7 +587,7 @@ addMessageListener("Browser:AppTab", function(message) {
|
||||
}
|
||||
});
|
||||
|
||||
let WebBrowserChrome = {
|
||||
var WebBrowserChrome = {
|
||||
onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
|
||||
return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
|
||||
},
|
||||
@@ -610,7 +610,7 @@ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
|
||||
}
|
||||
|
||||
|
||||
let DOMFullscreenHandler = {
|
||||
var DOMFullscreenHandler = {
|
||||
_fullscreenDoc: null,
|
||||
|
||||
init: function() {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var {WebChannel} = Cu.import("resource://gre/modules/WebChannel.jsm", {});
|
||||
|
||||
const TEST_URL_TAIL = "example.com/browser/browser/base/content/test/general/test_remoteTroubleshoot.html"
|
||||
const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URL_TAIL, null, null);
|
||||
const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URL_TAIL, null, null);
|
||||
|
||||
// Creates a one-shot web-channel for the test data to be sent back from the test page.
|
||||
function promiseChannelResponse(channelID, originOrPermission) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let channel = new WebChannel(channelID, originOrPermission);
|
||||
channel.listen((id, data, target) => {
|
||||
channel.stopListening();
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Loads the specified URI in a new tab and waits for it to send us data on our
|
||||
// test web-channel and resolves with that data.
|
||||
function promiseNewChannelResponse(uri) {
|
||||
let channelPromise = promiseChannelResponse("test-remote-troubleshooting-backchannel",
|
||||
uri);
|
||||
let tab = gBrowser.loadOneTab(uri.spec, { inBackground: false });
|
||||
return promiseTabLoaded(tab).then(
|
||||
() => channelPromise
|
||||
).then(data => {
|
||||
gBrowser.removeTab(tab);
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function*() {
|
||||
// We haven't set a permission yet - so even the "good" URI should fail.
|
||||
let got = yield promiseNewChannelResponse(TEST_URI_GOOD);
|
||||
// Should have no data.
|
||||
Assert.ok(got.message === undefined, "should have failed to get any data");
|
||||
|
||||
// Add a permission manager entry for our URI.
|
||||
Services.perms.add(TEST_URI_GOOD,
|
||||
"remote-troubleshooting",
|
||||
Services.perms.ALLOW_ACTION);
|
||||
registerCleanupFunction(() => {
|
||||
Services.perms.remove(TEST_URI_GOOD, "remote-troubleshooting");
|
||||
});
|
||||
|
||||
// Try again - now we are expecting a response with the actual data.
|
||||
got = yield promiseNewChannelResponse(TEST_URI_GOOD);
|
||||
|
||||
// Check some keys we expect to always get.
|
||||
Assert.ok(got.message.extensions, "should have extensions");
|
||||
Assert.ok(got.message.graphics, "should have graphics");
|
||||
|
||||
// Check we have channel and build ID info:
|
||||
Assert.equal(got.message.application.buildID, Services.appinfo.appBuildID,
|
||||
"should have correct build ID");
|
||||
|
||||
let updateChannel = null;
|
||||
try {
|
||||
updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel;
|
||||
} catch (ex) {}
|
||||
if (!updateChannel) {
|
||||
Assert.ok(!('updateChannel' in got.message.application),
|
||||
"should not have update channel where not available.");
|
||||
} else {
|
||||
Assert.equal(got.message.application.updateChannel, updateChannel,
|
||||
"should have correct update channel.");
|
||||
}
|
||||
|
||||
|
||||
// And check some keys we know we decline to return.
|
||||
Assert.ok(!got.message.modifiedPreferences, "should not have a modifiedPreferences key");
|
||||
Assert.ok(!got.message.crashes, "should not have crash info");
|
||||
|
||||
// Now a http:// URI - should get nothing even with the permission setup.
|
||||
got = yield promiseNewChannelResponse(TEST_URI_BAD);
|
||||
Assert.ok(got.message === undefined, "should have failed to get any data");
|
||||
});
|
||||
@@ -62,7 +62,7 @@ browser.jar:
|
||||
content/browser/content.js (content/content.js)
|
||||
content/browser/defaultthemes/devedition.header.png (content/defaultthemes/devedition.header.png)
|
||||
content/browser/defaultthemes/devedition.icon.png (content/defaultthemes/devedition.icon.png)
|
||||
content/browser/newtab/newTab.xul (content/newtab/newTab.xul)
|
||||
content/browser/newtab/newTab.xhtml (content/newtab/newTab.xhtml)
|
||||
* content/browser/newtab/newTab.js (content/newtab/newTab.js)
|
||||
content/browser/newtab/newTab.css (content/newtab/newTab.css)
|
||||
* content/browser/pageinfo/pageInfo.xul (content/pageinfo/pageInfo.xul)
|
||||
|
||||
@@ -82,7 +82,7 @@ static RedirEntry kRedirMap[] = {
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::MAKE_LINKABLE |
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "newtab", "chrome://browser/content/newtab/newTab.xul",
|
||||
{ "newtab", "chrome://browser/content/newtab/newTab.xhtml",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "permissions", "chrome://browser/content/preferences/aboutPermissions.xul",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
let { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
|
||||
@@ -97,6 +97,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
|
||||
"resource:///modules/ContentSearch.jsm");
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
|
||||
"resource:///modules/ContentCrashReporters.jsm");
|
||||
@@ -2633,15 +2638,18 @@ var DefaultBrowserCheck = {
|
||||
};
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
let E10SUINotification = {
|
||||
var E10SUINotification = {
|
||||
// Increase this number each time we want to roll out an
|
||||
// e10s testing period to Nightly users.
|
||||
CURRENT_NOTICE_COUNT: 0,
|
||||
CURRENT_NOTICE_COUNT: 1,
|
||||
CURRENT_PROMPT_PREF: "browser.displayedE10SPrompt.1",
|
||||
PREVIOUS_PROMPT_PREF: "browser.displayedE10SPrompt",
|
||||
|
||||
checkStatus: function() {
|
||||
let skipE10sChecks = false;
|
||||
try {
|
||||
let updateChannel = UpdateChannel.get();
|
||||
let updateChannel = UpdateUtils.UpdateChannel;
|
||||
let channelAuthorized = updateChannel == "nightly" || updateChannel == "aurora";
|
||||
|
||||
skipE10sChecks = !channelAuthorized ||
|
||||
@@ -2672,16 +2680,25 @@ let E10SUINotification = {
|
||||
} else {
|
||||
let e10sPromptShownCount = 0;
|
||||
try {
|
||||
e10sPromptShownCount = Services.prefs.getIntPref("browser.displayedE10SPrompt");
|
||||
e10sPromptShownCount = Services.prefs.getIntPref(this.CURRENT_PROMPT_PREF);
|
||||
} catch(e) {}
|
||||
|
||||
let isHardwareAccelerated = true;
|
||||
try {
|
||||
let win = RecentWindow.getMostRecentBrowserWindow();
|
||||
let winutils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
isHardwareAccelerated = winutils.layerManagerType != "Basic";
|
||||
} catch (e) {}
|
||||
|
||||
if (!Services.appinfo.inSafeMode &&
|
||||
!Services.appinfo.accessibilityEnabled &&
|
||||
isHardwareAccelerated &&
|
||||
e10sPromptShownCount < 5) {
|
||||
Services.tm.mainThread.dispatch(() => {
|
||||
try {
|
||||
this._showE10SPrompt();
|
||||
Services.prefs.setIntPref("browser.displayedE10SPrompt", e10sPromptShownCount + 1);
|
||||
Services.prefs.setIntPref(this.CURRENT_PROMPT_PREF, e10sPromptShownCount + 1);
|
||||
Services.prefs.clearUserPref(this.PREVIOUS_PROMPT_PREF);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Failed to show e10s prompt: " + ex);
|
||||
}
|
||||
@@ -2756,7 +2773,7 @@ let E10SUINotification = {
|
||||
label: win.gNavigatorBundle.getString("e10s.offerPopup.noThanks.label"),
|
||||
accessKey: win.gNavigatorBundle.getString("e10s.offerPopup.noThanks.accesskey"),
|
||||
callback: function () {
|
||||
Services.prefs.setIntPref("browser.displayedE10SPrompt", 5);
|
||||
Services.prefs.setIntPref(E10SUINotification.CURRENT_PROMPT_PREF, 5);
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -2766,7 +2783,7 @@ let E10SUINotification = {
|
||||
persistWhileVisible: true
|
||||
};
|
||||
|
||||
win.PopupNotifications.show(browser, "enable_e10s", promptMessage, null, mainAction, secondaryActions, options);
|
||||
win.PopupNotifications.show(browser, "enable-e10s", promptMessage, null, mainAction, secondaryActions, options);
|
||||
|
||||
let highlights = [
|
||||
win.gNavigatorBundle.getString("e10s.offerPopup.highlight1"),
|
||||
|
||||
@@ -7,3 +7,16 @@ newtab.unpin=Unpin this site
|
||||
newtab.block=Remove this site
|
||||
newtab.show=Show the new tab page
|
||||
newtab.hide=Hide the new tab page
|
||||
# LOCALIZATION NOTE(newtab.sponsored.button): This text appears for sponsored
|
||||
# and enhanced tiles on the same line as the tile's title, so prefer short
|
||||
# strings to avoid overlap. This string should be uppercase.
|
||||
newtab.sponsored.button=SPONSORED
|
||||
# LOCALIZATION NOTE(newtab.sponsored.explain): %1$S will be replaced inline by
|
||||
# the (X) block icon. %2$S will be replaced by an active link using string
|
||||
# newtab.learn.link as text.
|
||||
newtab.sponsored.explain=This tile is being shown to you on behalf of a Mozilla partner. You can remove it at any time by clicking the %1$S button. %2$S
|
||||
# LOCALIZATION NOTE(newtab.enhanced.explain): %1$S will be replaced inline by
|
||||
# the gear icon used to customize the new tab window. %2$S will be replaced by
|
||||
# an active link using string newtab.learn.link as text.
|
||||
newtab.enhanced.explain=A Mozilla partner has visually enhanced this tile, replacing the screenshot. You can turn off enhanced tiles by clicking the %1$S button for your preferences. %2$S
|
||||
newtab.learn.link=Learn more…
|
||||
|
||||
@@ -26,8 +26,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm")
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
"resource://gre/modules/UpdateChannel.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "eTLD",
|
||||
"@mozilla.org/network/effective-tld-service;1",
|
||||
"nsIEffectiveTLDService");
|
||||
@@ -280,7 +280,7 @@ var DirectoryLinksProvider = {
|
||||
_fetchAndCacheLinks: function DirectoryLinksProvider_fetchAndCacheLinks(uri) {
|
||||
// Replace with the same display locale used for selecting links data
|
||||
uri = uri.replace("%LOCALE%", this.locale);
|
||||
uri = uri.replace("%CHANNEL%", UpdateChannel.get());
|
||||
uri = uri.replace("%CHANNEL%", UpdateUtils.UpdateChannel);
|
||||
|
||||
return this._downloadJsonData(uri).then(json => {
|
||||
return OS.File.writeAtomic(this._directoryFilePath, json, {tmpPath: this._directoryFilePath + ".tmp"});
|
||||
|
||||
@@ -25,7 +25,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
*/
|
||||
const HANG_EXPIRATION_TIME = 10000;
|
||||
|
||||
let ProcessHangMonitor = {
|
||||
var ProcessHangMonitor = {
|
||||
/**
|
||||
* Collection of hang reports that haven't expired or been dismissed
|
||||
* by the user. The keys are nsIHangReports and values keys are
|
||||
|
||||
@@ -85,7 +85,6 @@ browser.jar:
|
||||
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
|
||||
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
|
||||
skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
|
||||
skin/classic/browser/newtab/controls.png (newtab/controls.png)
|
||||
skin/classic/browser/newtab/noise.png (newtab/noise.png)
|
||||
skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
|
||||
skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
|
||||
|
||||
@@ -105,7 +105,6 @@ browser.jar:
|
||||
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
|
||||
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
|
||||
skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
|
||||
skin/classic/browser/newtab/controls.png (newtab/controls.png)
|
||||
skin/classic/browser/newtab/noise.png (newtab/noise.png)
|
||||
skin/classic/browser/places/places.css (places/places.css)
|
||||
* skin/classic/browser/places/organizer.css (places/organizer.css)
|
||||
|
||||
@@ -19,6 +19,17 @@
|
||||
skin/classic/browser/addons/addon-install-anchor.svg (../shared/addons/addon-install-anchor.svg)
|
||||
skin/classic/browser/fullscreen/insecure.svg (../shared/fullscreen/insecure.svg)
|
||||
skin/classic/browser/fullscreen/secure.svg (../shared/fullscreen/secure.svg)
|
||||
skin/classic/browser/search-indicator.png (../shared/search/search-indicator.png)
|
||||
skin/classic/browser/search-indicator@2x.png (../shared/search/search-indicator@2x.png)
|
||||
skin/classic/browser/search-engine-placeholder.png (../shared/search/search-engine-placeholder.png)
|
||||
skin/classic/browser/search-engine-placeholder@2x.png (../shared/search/search-engine-placeholder@2x.png)
|
||||
skin/classic/browser/badge-add-engine.png (../shared/search/badge-add-engine.png)
|
||||
skin/classic/browser/badge-add-engine@2x.png (../shared/search/badge-add-engine@2x.png)
|
||||
skin/classic/browser/search-indicator-badge-add.png (../shared/search/search-indicator-badge-add.png)
|
||||
skin/classic/browser/search-indicator-badge-add@2x.png (../shared/search/search-indicator-badge-add@2x.png)
|
||||
skin/classic/browser/search-history-icon.svg (../shared/search/history-icon.svg)
|
||||
skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg)
|
||||
skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg)
|
||||
skin/classic/browser/reader-mode-16.png (../shared/reader/reader-mode-16.png)
|
||||
skin/classic/browser/session-restore.svg (../shared/incontent-icons/session-restore.svg)
|
||||
skin/classic/browser/tab-crashed.svg (../shared/incontent-icons/tab-crashed.svg)
|
||||
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 425 B |
|
After Width: | Height: | Size: 888 B |
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
use:not(:target) {
|
||||
display: none;
|
||||
}
|
||||
use {
|
||||
fill: graytext;
|
||||
}
|
||||
use[id$="-active"] {
|
||||
fill: HighlightText;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<path id="search-history-glyph" d="M8,1C4.1,1,1,4.1,1,8c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7 C15,4.1,11.9,1,8,1z M8,13.3c-2.9,0-5.3-2.4-5.3-5.3S5.1,2.7,8,2.7c2.9,0,5.3,2.4,5.3,5.3S10.9,13.3,8,13.3z M10.5,7H9V5 c0-0.6-0.4-1-1-1S7,4.4,7,5v3c0,0.6,0.4,1,1,1h2.5c0.6,0,1-0.4,1-1C11.5,7.4,11.1,7,10.5,7z"/>
|
||||
</defs>
|
||||
<use id="search-history-icon" xlink:href="#search-history-glyph"/>
|
||||
<use id="search-history-icon-active" xlink:href="#search-history-glyph"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1000 B |
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
use:not(:target) {
|
||||
display: none;
|
||||
}
|
||||
use {
|
||||
fill: #616366;
|
||||
}
|
||||
use[id$="-inverted"] {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<path id="search-arrow-go-glyph" d="M1,7v2.2C1,9.8,1.4,10,2,10h7.5l-3,3.1c-0.4,0.3-0.4,1,0,1.4l0.8,0.8 c0.4,0.4,1,0.4,1.4,0l6.6-6.6c0.4-0.4,0.4-1,0-1.4L8.7,0.7c-0.4-0.4-1-0.4-1.4,0L6.5,1.6C6.1,2,6.1,2.6,6.5,3l3,3H2C1.4,6,1,6.4,1,7z"/>
|
||||
</defs>
|
||||
<use id="search-arrow-go" xlink:href="#search-arrow-go-glyph"/>
|
||||
<use id="search-arrow-go-inverted" xlink:href="#search-arrow-go-glyph"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 926 B |
|
After Width: | Height: | Size: 252 B |
|
After Width: | Height: | Size: 461 B |
|
After Width: | Height: | Size: 1000 B |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="#808080" d="M21.7,20.3l-1.4,1.4l-5.4-5.4c-1.3,1-3,1.7-4.9,1.7 c-4.4,0-8-3.6-8-8c0-4.4,3.6-8,8-8c4.4,0,8,3.6,8,8c0,1.8-0.6,3.5-1.7,4.9L21.7,20.3z M10,4c-3.3,0-6,2.7-6,6s2.7,6,6,6s6-2.7,6-6 S13.3,4,10,4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 561 B |
|
After Width: | Height: | Size: 344 B |
|
After Width: | Height: | Size: 694 B |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -116,7 +116,6 @@ browser.jar:
|
||||
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
|
||||
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
|
||||
skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
|
||||
skin/classic/browser/newtab/controls.png (newtab/controls.png)
|
||||
skin/classic/browser/newtab/noise.png (newtab/noise.png)
|
||||
skin/classic/browser/places/places.css (places/places.css)
|
||||
skin/classic/browser/places/organizer.css (places/organizer.css)
|
||||
|
||||
@@ -17,7 +17,7 @@ import traceback
|
||||
import zipfile
|
||||
|
||||
from automation import Automation
|
||||
from mozlog.structured import get_default_logger
|
||||
from mozlog import get_default_logger
|
||||
from mozprocess import ProcessHandlerMixin
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import sys
|
||||
|
||||
from automation import Automation
|
||||
from devicemanager import DMError, DeviceManager
|
||||
from mozlog.structured import get_default_logger
|
||||
from mozlog import get_default_logger
|
||||
import mozcrash
|
||||
|
||||
# signatures for logcat messages that we don't care about much
|
||||
|
||||
@@ -497,6 +497,17 @@ this.PermissionsTable = { geolocation: {
|
||||
trusted: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"system-app-only-audio-channels-in-app": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"killswitch": {
|
||||
app: DENY_ACTION,
|
||||
trusted: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4528,6 +4528,25 @@ this.DOMApplicationRegistry = {
|
||||
}
|
||||
},
|
||||
|
||||
// Returns a promise that resolves once all the add-ons are disabled.
|
||||
disableAllAddons: function() {
|
||||
for (let id in this.webapps) {
|
||||
let app = this.webapps[id];
|
||||
if (app.role == "addon" && app.enabled) {
|
||||
app.enabled = false;
|
||||
MessageBroadcaster.broadcastMessage("Webapps:UpdateState", {
|
||||
app: app,
|
||||
id: app.id
|
||||
});
|
||||
MessageBroadcaster.broadcastMessage("Webapps:SetEnabled:Return", app);
|
||||
|
||||
UserCustomizations.unregister(app);
|
||||
}
|
||||
}
|
||||
|
||||
return this._saveApps();
|
||||
},
|
||||
|
||||
getManifestFor: function(aManifestURL, aEntryPoint) {
|
||||
let id = this._appIdForManifestURL(aManifestURL);
|
||||
let app = this.webapps[id];
|
||||
|
||||
@@ -45,6 +45,24 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(BrowserElementAudioChannel,
|
||||
mTabParent,
|
||||
mBrowserElementAPI)
|
||||
|
||||
/* static */ already_AddRefed<BrowserElementAudioChannel>
|
||||
BrowserElementAudioChannel::Create(nsPIDOMWindow* aWindow,
|
||||
nsIFrameLoader* aFrameLoader,
|
||||
nsIBrowserElementAPI* aAPI,
|
||||
AudioChannel aAudioChannel,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsRefPtr<BrowserElementAudioChannel> ac =
|
||||
new BrowserElementAudioChannel(aWindow, aFrameLoader, aAPI, aAudioChannel);
|
||||
|
||||
aRv = ac->Initialize();
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ac.forget();
|
||||
}
|
||||
|
||||
BrowserElementAudioChannel::BrowserElementAudioChannel(
|
||||
nsPIDOMWindow* aWindow,
|
||||
nsIFrameLoader* aFrameLoader,
|
||||
@@ -58,7 +76,6 @@ BrowserElementAudioChannel::BrowserElementAudioChannel(
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
AssertIsInMainProcess();
|
||||
MOZ_ASSERT(mFrameLoader);
|
||||
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
if (obs) {
|
||||
@@ -94,6 +111,17 @@ BrowserElementAudioChannel::~BrowserElementAudioChannel()
|
||||
nsresult
|
||||
BrowserElementAudioChannel::Initialize()
|
||||
{
|
||||
if (!mFrameLoader) {
|
||||
nsCOMPtr<nsPIDOMWindow> window = GetOwner();
|
||||
if (!window) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mFrameWindow = window->GetScriptableTop();
|
||||
mFrameWindow = mFrameWindow->GetOuterWindow();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDocShell> docShell;
|
||||
nsresult rv = mFrameLoader->GetDocShell(getter_AddRefs(docShell));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
|
||||