Bug 1181561 - Expose a Kill Switch enabling/disabling. r=dhylands, sr=sicking

This commit is contained in:
Alexandre Lissy 2015-08-26 03:11:00 -04:00
parent 21040381bc
commit 5c27fc8460
20 changed files with 1700 additions and 1 deletions

View File

@ -23,6 +23,7 @@ Cu.import('resource://gre/modules/SystemUpdateService.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');

View File

@ -121,3 +121,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}

View File

@ -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]);

View File

@ -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");
let 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();
}

View File

@ -18,6 +18,7 @@ EXTRA_COMPONENTS += [
'FxAccountsUIGlue.js',
'HelperAppDialog.js',
'InterAppCommUIGlue.js',
'KillSwitch.js',
'MailtoProtocolHandler.js',
'MobileIdentityUIGlue.js',
'OMAContentHandler.js',
@ -70,6 +71,10 @@ EXTRA_JS_MODULES += [
'WebappsUpdater.jsm',
]
EXTRA_PP_JS_MODULES += [
'KillSwitchMain.jsm'
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
EXTRA_JS_MODULES += [
'GlobalSimulatorScreen.jsm'

View File

@ -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;
});
}

View File

@ -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);
}

View File

@ -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]

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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();
});
});
}

View File

@ -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();

View File

@ -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();

View File

@ -41,3 +41,12 @@ head = head_logshake_gonk.js
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))

View File

@ -961,6 +961,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

View File

@ -497,6 +497,12 @@ this.PermissionsTable = { geolocation: {
trusted: DENY_ACTION,
privileged: ALLOW_ACTION,
certified: ALLOW_ACTION
},
"killswitch": {
app: DENY_ACTION,
trusted: DENY_ACTION,
privileged: DENY_ACTION,
certified: ALLOW_ACTION
}
};

View File

@ -0,0 +1,24 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/.
*/
/**
* This API is intended to lock the device in a way that it has no value
* anymore for a thief. A side effect is also protecting user's data. This
* means that we expect the device to be:
* - locked so that only the legitimate user can unlock it
* - unable to communitate via ADB, Devtools, MTP and/or UMS, ...
* - unable to go into recovery or fastboot mode to avoid flashing anything
*/
[JSImplementation="@mozilla.org/moz-kill-switch;1",
NavigatorProperty="mozKillSwitch",
AvailableIn="CertifiedApps",
CheckAnyPermissions="killswitch",
Pref="dom.mozKillSwitch.enabled"]
interface KillSwitch {
Promise<any> enable();
Promise<any> disable();
};

View File

@ -276,6 +276,7 @@ WEBIDL_FILES = [
'KeyboardEvent.webidl',
'KeyEvent.webidl',
'KeyframeEffect.webidl',
'KillSwitch.webidl',
'LegacyQueryInterface.webidl',
'LinkStyle.webidl',
'ListBoxObject.webidl',

View File

@ -5073,3 +5073,5 @@ pref("media.useAudioChannelAPI", false);
// Expose Request.context. Currently disabled since the spec is in flux.
pref("dom.requestcontext.enabled", false);
pref("dom.mozKillSwitch.enabled", false);