Bug 1149868 - Move permissionObserver to SpecialPowersObserver to listen all perm-changed signals. r=jmaher

This commit is contained in:
chunminchang 2015-05-24 18:49:00 -04:00
parent 874588c43a
commit e7ffccf507
8 changed files with 310 additions and 9 deletions

View File

@ -0,0 +1,9 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>empty app</title>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,55 @@
var gBasePath = "tests/testing/mochitest/tests/Harness_sanity/";
function handleRequest(request, response) {
var query = getQuery(request);
var testToken = '';
if ('testToken' in query) {
testToken = query.testToken;
}
var template = '';
if ('template' in query) {
template = query.template;
}
var template = gBasePath + template;
response.setHeader("Content-Type", "application/x-web-app-manifest+json", false);
response.write(readTemplate(template).replace(/TESTTOKEN/g, testToken));
}
// Copy-pasted incantations. There ought to be a better way to synchronously read
// a file into a string, but I guess we're trying to discourage that.
function readTemplate(path) {
var file = Components.classes["@mozilla.org/file/directory_service;1"].
getService(Components.interfaces.nsIProperties).
get("CurWorkD", Components.interfaces.nsILocalFile);
var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
createInstance(Components.interfaces.nsIFileInputStream);
var cis = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Components.interfaces.nsIConverterInputStream);
var split = path.split("/");
for(var i = 0; i < split.length; ++i) {
file.append(split[i]);
}
fis.init(file, -1, -1, false);
cis.init(fis, "UTF-8", 0, 0);
var data = "";
let str = {};
let read = 0;
do {
read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value
data += str.value;
} while (read != 0);
cis.close();
return data;
}
function getQuery(request) {
var query = {};
request.queryString.split('&').forEach(function (val) {
var [name, value] = val.split('=');
query[name] = unescape(value);
});
return query;
}

View File

@ -0,0 +1,6 @@
{
"name": "EMPTY-APP",
"description": "Empty app for Harness_sanity.",
"launch_path": "/tests/testing/mochitest/tests/Harness_sanity/TESTTOKEN",
"icons": { "128": "default_icon" }
}

View File

@ -12,6 +12,11 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
[test_SpecialPowersExtension2.html]
support-files = file_SpecialPowersFrame1.html
[test_SpecialPowersPushPermissions.html]
[test_SpecialPowersPushAppPermissions.html]
support-files =
file_app.sjs
file_app.template.webapp
app.html
[test_SpecialPowersPushPrefEnv.html]
[test_SimpletestGetTestFileURL.html]
[test_SpecialPowersLoadChromeScript.html]

View File

@ -0,0 +1,111 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for SpecialPowers extension</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="pushPermissionsToSelf();">
<pre id="test">
<script class="testbody" type="text/javascript">
// Used to access App's information like appId
const gAppsService = SpecialPowers.Cc["@mozilla.org/AppsService;1"]
.getService(SpecialPowers.Ci.nsIAppsService);
var gApp;
const gAppSource = 'app.html';
const FILE_APP = 'file_app.sjs';
const WEBAPP_TEMPLATE = 'file_app.template.webapp';
const DIR_PATH = 'testing/mochitest/tests/Harness_sanity/';
// The base path must be same as the gBasePath in file_app.sjs and the
// launch_path before TESTTOKEN in file_app.template.webapp
const BASE_PATH = '/tests/' + DIR_PATH;
const FILE_APP_PATH = BASE_PATH + FILE_APP;
SimpleTest.waitForExplicitFinish();
function cbError(e) {
ok(false, "Error callback invoked: " + this.error.name);
SimpleTest.finish();
}
function pushPermissionsToSelf() {
SpecialPowers.pushPermissions([
{ "type": "pAppPermission", "allow": true, "context": document }
], allowManagingApps);
}
// Setup the prefrences and permissions
function allowManagingApps() {
SpecialPowers.pushPermissions([
{ "type": "webapps-manage", "allow": true, "context": document }
], function() {
SpecialPowers.setAllAppsLaunchable(true);
// No confirmation needed when an app is installed and uninstalled.
SpecialPowers.autoConfirmAppInstall(() => {
SpecialPowers.autoConfirmAppUninstall(
function() {
installApp(gAppSource, WEBAPP_TEMPLATE);
}
);
});
}
);
}
function installApp(aTestToken, aTemplate) {
// Install App from manifest
var hostedManifestURL = window.location.origin + '/' + FILE_APP_PATH +
'?testToken=' + aTestToken + '&template=' + aTemplate;
var request = navigator.mozApps.install(hostedManifestURL);
request.onerror = cbError;
request.onsuccess = function() {
// Get installed app
gApp = request.result; // Assign to global variable
pushPermissionsToApp();
}
}
function pushPermissionsToApp() {
var appId = gAppsService.getAppLocalIdByManifestURL(gApp.manifestURL);
var context = { url: gApp.origin,
appId: appId,
isInBrowserElement: false };
SpecialPowers.pushPermissions([
{ "type": "pAppPermission", "allow": true, "context": context }
], testPermissionsForApp);
}
function testPermissionsForApp() {
var appId = gAppsService.getAppLocalIdByManifestURL(gApp.manifestURL);
var context = { url: gApp.origin,
appId: appId,
isInBrowserElement: false };
ok(SpecialPowers.hasPermission('pAppPermission', context), 'pAppPermission should have permission');
uninstallApp();
}
function uninstallApp() {
var request = navigator.mozApps.mgmt.uninstall(gApp);
request.onerror = cbError;
request.onsuccess = function() {
testPermissionsForSelfAndApp();
}
}
function testPermissionsForSelfAndApp() {
var appId = gAppsService.getAppLocalIdByManifestURL(gApp.manifestURL);
var context = { url: gApp.origin,
appId: appId,
isInBrowserElement: false };
ok(!SpecialPowers.hasPermission('pAppPermission', context), 'pAppPermission should not have permission');
ok(SpecialPowers.hasPermission('pAppPermission', document), 'pAppPermission should have permission');
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View File

@ -146,6 +146,9 @@ SpecialPowersObserver.prototype = new SpecialPowersObserverAPI();
obs.removeObserver(this, "chrome-document-global-created");
obs.removeObserver(this, "http-on-modify-request");
obs.removeObserver(this, "xpcom-shutdown");
this._registerObservers._topics.forEach(function(element) {
obs.removeObserver(this._registerObservers, element);
});
this._removeProcessCrashObservers();
if (this._isFrameScriptLoaded) {
@ -200,6 +203,27 @@ SpecialPowersObserver.prototype = new SpecialPowersObserverAPI();
this._processCrashObserversRegistered = false;
};
SpecialPowersObserver.prototype._registerObservers = {
_self: null,
_topics: [],
_add: function(topic) {
if (this._topics.indexOf(topic) < 0) {
this._topics.push(topic);
Services.obs.addObserver(this, topic, false);
}
},
observe: function (aSubject, aTopic, aData) {
var msg = { aData: aData };
switch (aTopic) {
case "perm-changed":
var permission = aSubject.QueryInterface(Ci.nsIPermission);
msg.permission = { appId: permission.appId, type: permission.type };
default:
this._self._sendAsyncMessage("specialpowers-" + aTopic, msg);
}
}
};
/**
* messageManager callback function
* This will get requests from our API in the window and process them in chrome for it

View File

@ -409,12 +409,16 @@ SpecialPowersObserverAPI.prototype = {
}
case "SPObserverService": {
let topic = aMessage.json.observerTopic;
switch (aMessage.json.op) {
case "notify":
let topic = aMessage.json.observerTopic;
let data = aMessage.json.observerData
Services.obs.notifyObservers(null, topic, data);
break;
case "add":
this._registerObservers._self = this;
this._registerObservers._add(topic);
break;
default:
throw new SpecialPowersError("Invalid operation for SPObserverervice");
}

View File

@ -44,6 +44,7 @@ function SpecialPowersAPI() {
this._permissionsUndoStack = [];
this._pendingPermissions = [];
this._applyingPermissions = false;
this._observingPermissions = false;
this._fm = null;
this._cb = null;
this._quotaManagerCallbackInfos = null;
@ -830,6 +831,21 @@ SpecialPowersAPI.prototype = {
// that the callback checks for. The second delay is because pref
// observers often defer making their changes by posting an event to the
// event loop.
if (!this._observingPermissions) {
this._observingPermissions = true;
// If specialpowers is in main-process, then we can add a observer
// to get all 'perm-changed' signals. Otherwise, it can't receive
// all signals, so we register a observer in specialpowersobserver(in
// main-process) and get signals from it.
if (this.isMainProcess()) {
this.permissionObserverProxy._specialPowersAPI = this;
Services.obs.addObserver(this.permissionObserverProxy, "perm-changed", false);
} else {
this.registerObservers("perm-changed");
// bind() is used to set 'this' to SpecialPowersAPI itself.
this._addMessageListener("specialpowers-perm-changed", this.permChangedProxy.bind(this));
}
}
this._permissionsUndoStack.push(cleanupPermissions);
this._pendingPermissions.push([pendingPermissions,
this._delayCallbackTwice(callback)]);
@ -839,6 +855,51 @@ SpecialPowersAPI.prototype = {
}
},
/*
* This function should be used when specialpowers is in content process but
* it want to get the notification from chrome space.
*
* This function will call Services.obs.addObserver in SpecialPowersObserver
* (that is in chrome process) and forward the data received to SpecialPowers
* via messageManager.
* You can use this._addMessageListener("specialpowers-YOUR_TOPIC") to fire
* the callback.
*
* To get the expected data, you should modify
* SpecialPowersObserver.prototype._registerObservers.observe. Or the message
* you received from messageManager will only contain 'aData' from Service.obs.
*
* NOTICE: there is no implementation of _addMessageListener in
* ChromePowers.js
*/
registerObservers: function(topic) {
var msg = {
'op': 'add',
'observerTopic': topic,
};
this._sendSyncMessage("SPObserverService", msg);
},
permChangedProxy: function(aMessage) {
let permission = aMessage.json.permission;
let aData = aMessage.json.aData;
this._permissionObserver.observe(permission, aData);
},
permissionObserverProxy: {
// 'this' in permChangedObserverProxy is the permChangedObserverProxy
// object itself. The '_specialPowersAPI' will be set to the 'SpecialPowersAPI'
// object to call the member function in SpecialPowersAPI.
_specialPowersAPI: null,
observe: function (aSubject, aTopic, aData)
{
if (aTopic == "perm-changed") {
var permission = aSubject.QueryInterface(Ci.nsIPermission);
this._specialPowersAPI._permissionObserver.observe(permission, aData);
}
}
},
popPermissions: function(callback) {
if (this._permissionsUndoStack.length > 0) {
// See pushPermissions comment regarding delay.
@ -847,7 +908,11 @@ SpecialPowersAPI.prototype = {
this._pendingPermissions.push([this._permissionsUndoStack.pop(), cb]);
this._applyPermissions();
} else {
this._setTimeout(callback);
if (this._observingPermissions) {
this._observingPermissions = false;
this._removeMessageListener("specialpowers-perm-changed", this.permChangedProxy.bind(this));
}
this._setTimeout(callback);
}
},
@ -870,15 +935,39 @@ SpecialPowersAPI.prototype = {
_lastPermission: {},
_callBack: null,
_nextCallback: null,
observe: function (aSubject, aTopic, aData)
_obsDataMap: {
'deleted':'remove',
'added':'add'
},
observe: function (permission, aData)
{
if (aTopic == "perm-changed") {
var permission = aSubject.QueryInterface(Ci.nsIPermission);
if (this._self._applyingPermissions) {
if (permission.type == this._lastPermission.type) {
Services.obs.removeObserver(this, "perm-changed");
this._self._setTimeout(this._callback);
this._self._setTimeout(this._nextCallback);
this._callback = null;
this._nextCallback = null;
}
} else {
var found = false;
for (var i = 0; !found && i < this._self._permissionsUndoStack.length; i++) {
var undos = this._self._permissionsUndoStack[i];
for (var j = 0; j < undos.length; j++) {
var undo = undos[j];
if (undo.op == this._obsDataMap[aData] &&
undo.appId == permission.appId &&
undo.type == permission.type) {
// Remove this undo item if it has been done by others(not
// specialpowers itself.)
undos.splice(j,1);
found = true;
break;
}
}
if (!undos.length) {
// Remove the empty row in permissionsUndoStack
this._self._permissionsUndoStack.splice(i, 1);
}
}
}
}
@ -910,8 +999,6 @@ SpecialPowersAPI.prototype = {
self._applyPermissions();
}
Services.obs.addObserver(this._permissionObserver, "perm-changed", false);
for (var idx in pendingActions) {
var perm = pendingActions[idx];
this._sendSyncMessage('SPPermissionManager', perm)[0];