Bug 340432 - Add the ability to cancel site permission changes. r=Unfocused

This commit is contained in:
Michael Lopez 2015-02-23 17:07:00 +01:00
parent 535957d100
commit fd83b22816
4 changed files with 342 additions and 264 deletions

View File

@ -3,6 +3,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/. */
Components.utils.import("resource://gre/modules/Services.jsm");
const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
const nsICookiePermission = Components.interfaces.nsICookiePermission;
@ -17,12 +19,13 @@ function Permission(host, rawHost, type, capability)
}
var gPermissionManager = {
_type : "",
_permissions : [],
_pm : Components.classes["@mozilla.org/permissionmanager;1"]
.getService(Components.interfaces.nsIPermissionManager),
_bundle : null,
_tree : null,
_type : "",
_permissions : [],
_permissionsToAdd : new Map(),
_permissionsToDelete : new Map(),
_bundle : null,
_tree : null,
_observerRemoved : false,
_view: {
_rowCount: 0,
@ -82,38 +85,41 @@ var gPermissionManager = {
var textbox = document.getElementById("url");
var host = textbox.value.replace(/^\s*([-\w]*:\/+)?/, ""); // trim any leading space and scheme
try {
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var uri = ioService.newURI("http://"+host, null, null);
var uri = Services.io.newURI("http://"+host, null, null);
host = uri.host;
} catch(ex) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var message = this._bundle.getString("invalidURI");
var title = this._bundle.getString("invalidURITitle");
promptService.alert(window, title, message);
Services.prompt.alert(window, title, message);
return;
}
var capabilityString = this._getCapabilityString(aCapability);
// check whether the permission already exists, if not, add it
var exists = false;
let hostExists = false;
let capabilityExists = false;
for (var i = 0; i < this._permissions.length; ++i) {
if (this._permissions[i].rawHost == host) {
// Avoid calling the permission manager if the capability settings are
// the same. Otherwise allow the call to the permissions manager to
// update the listbox for us.
exists = this._permissions[i].capability == capabilityString;
hostExists = true;
capabilityExists = this._permissions[i].capability == capabilityString;
if (!capabilityExists) {
this._permissions[i].capability = capabilityString;
}
break;
}
}
if (!exists) {
host = (host.charAt(0) == ".") ? host.substring(1,host.length) : host;
var uri = ioService.newURI("http://" + host, null, null);
this._pm.add(uri, this._type, aCapability);
let permissionParams = {host: host, type: this._type, capability: aCapability};
if (!hostExists) {
this._permissionsToAdd.set(host, permissionParams);
this._addPermission(permissionParams);
}
else if (!capabilityExists) {
this._permissionsToAdd.set(host, permissionParams);
this._handleCapabilityChange();
}
textbox.value = "";
textbox.focus();
@ -124,6 +130,49 @@ var gPermissionManager = {
document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0;
},
_removePermission: function(aPermission)
{
this._removePermissionFromList(aPermission.host);
// If this permission was added during this session, let's remove
// it from the pending adds list to prevent calls to the
// permission manager.
let isNewPermission = this._permissionsToAdd.delete(aPermission.host);
if (!isNewPermission) {
this._permissionsToDelete.set(aPermission.host, aPermission);
}
},
_handleCapabilityChange: function ()
{
// Re-do the sort, if the status changed from Block to Allow
// or vice versa, since if we're sorted on status, we may no
// longer be in order.
if (this._lastPermissionSortColumn.id == "statusCol") {
this._resortPermissions();
}
this._tree.treeBoxObject.invalidate();
},
_addPermission: function(aPermission)
{
this._addPermissionToList(aPermission);
++this._view._rowCount;
this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1);
// Re-do the sort, since we inserted this new item at the end.
this._resortPermissions();
},
_resortPermissions: function()
{
gTreeUtils.sort(this._tree, this._view, this._permissions,
this._permissionsComparator,
this._lastPermissionSortColumn,
this._lastPermissionSortAscending);
},
onHostInput: function (aSiteField)
{
document.getElementById("btnSession").disabled = !aSiteField.value;
@ -182,10 +231,8 @@ var gPermissionManager = {
var urlLabel = document.getElementById("urlLabel");
urlLabel.hidden = !urlFieldVisible;
var os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
os.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type);
os.addObserver(this, "perm-changed", false);
Services.obs.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type);
Services.obs.addObserver(this, "perm-changed", false);
this._loadPermissions();
@ -194,9 +241,11 @@ var gPermissionManager = {
uninit: function ()
{
var os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
os.removeObserver(this, "perm-changed");
if (!this._observerRemoved) {
Services.obs.removeObserver(this, "perm-changed");
this._observerRemoved = true;
}
},
observe: function (aSubject, aTopic, aData)
@ -209,14 +258,7 @@ var gPermissionManager = {
return;
if (aData == "added") {
this._addPermissionToList(permission);
++this._view._rowCount;
this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1);
// Re-do the sort, since we inserted this new item at the end.
gTreeUtils.sort(this._tree, this._view, this._permissions,
this._permissionsComparator,
this._lastPermissionSortColumn,
this._lastPermissionSortAscending);
this._addPermission(permission);
}
else if (aData == "changed") {
for (var i = 0; i < this._permissions.length; ++i) {
@ -225,27 +267,10 @@ var gPermissionManager = {
break;
}
}
// Re-do the sort, if the status changed from Block to Allow
// or vice versa, since if we're sorted on status, we may no
// longer be in order.
if (this._lastPermissionSortColumn.id == "statusCol") {
gTreeUtils.sort(this._tree, this._view, this._permissions,
this._permissionsComparator,
this._lastPermissionSortColumn,
this._lastPermissionSortAscending);
}
this._tree.treeBoxObject.invalidate();
this._handleCapabilityChange();
}
else if (aData == "deleted") {
for (var i = 0; i < this._permissions.length; i++) {
if (this._permissions[i].host == permission.host) {
this._permissions.splice(i, 1);
this._view._rowCount--;
this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, -1);
this._tree.treeBoxObject.invalidate();
break;
}
}
this._removePermissionFromList(permission);
}
}
},
@ -266,7 +291,7 @@ var gPermissionManager = {
gTreeUtils.deleteSelectedItems(this._tree, this._view, this._permissions, removedPermissions);
for (var i = 0; i < removedPermissions.length; ++i) {
var p = removedPermissions[i];
this._pm.remove(p.host, p.type);
this._removePermission(p);
}
document.getElementById("removePermission").disabled = !this._permissions.length;
document.getElementById("removeAllPermissions").disabled = !this._permissions.length;
@ -280,7 +305,7 @@ var gPermissionManager = {
gTreeUtils.deleteAll(this._tree, this._view, this._permissions, removedPermissions);
for (var i = 0; i < removedPermissions.length; ++i) {
var p = removedPermissions[i];
this._pm.remove(p.host, p.type);
this._removePermission(p);
}
document.getElementById("removePermission").disabled = true;
document.getElementById("removeAllPermissions").disabled = true;
@ -316,6 +341,25 @@ var gPermissionManager = {
this._lastPermissionSortColumn = aColumn;
},
onApplyChanges: function()
{
// Stop observing permission changes since we are about
// to write out the pending adds/deletes and don't need
// to update the UI
this.uninit();
for (let permissionParams of this._permissionsToAdd.values()) {
let uri = Services.io.newURI("http://" + permissionParams.host, null, null);
Services.perms.add(uri, permissionParams.type, permissionParams.capability);
}
for (let p of this._permissionsToDelete.values()) {
Services.perms.remove(p.host, p.type);
}
window.close();
},
_loadPermissions: function ()
{
this._tree = document.getElementById("permissionsTree");
@ -323,7 +367,7 @@ var gPermissionManager = {
// load permissions into a table
var count = 0;
var enumerator = this._pm.enumerator;
var enumerator = Services.perms.enumerator;
while (enumerator.hasMoreElements()) {
var nextPermission = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
this._addPermissionToList(nextPermission);
@ -355,6 +399,19 @@ var gPermissionManager = {
}
},
_removePermissionFromList: function (aHost)
{
for (let i = 0; i < this._permissions.length; ++i) {
if (this._permissions[i].host == aHost) {
this._permissions.splice(i, 1);
this._view._rowCount--;
this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, -1);
this._tree.treeBoxObject.invalidate();
break;
}
}
},
setHost: function (aHost)
{
document.getElementById("url").value = aHost;

View File

@ -61,8 +61,8 @@
<treechildren/>
</tree>
</vbox>
<hbox align="end">
<hbox class="actionButtons" flex="1">
<vbox>
<hbox class="actionButtons" align="left" flex="1">
<button id="removePermission" disabled="true"
accesskey="&removepermission.accesskey;"
icon="remove" label="&removepermission.label;"
@ -71,9 +71,13 @@
icon="clear" label="&removeallpermissions.label;"
accesskey="&removeallpermissions.accesskey;"
oncommand="gPermissionManager.onAllPermissionsDeleted();"/>
<spacer flex="1"/>
<button oncommand="close();" icon="close"
label="&button.close.label;" accesskey="&button.close.accesskey;"/>
</hbox>
</hbox>
<spacer flex="1"/>
<hbox class="actionButtons" align="right" flex="1">
<button oncommand="close();" icon="close"
label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" />
<button id="btnApplyChanges" oncommand="gPermissionManager.onApplyChanges();" icon="save"
label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
</hbox>
</vbox>
</window>

View File

@ -4,182 +4,197 @@
function test() {
waitForExplicitFinish();
function prefWindowObserver(subject, topic, data) {
if (topic != "domwindowopened")
return;
Services.ww.unregisterNotification(this);
let win = subject.QueryInterface(Ci.nsIDOMEventTarget);
win.addEventListener("load", function(event) {
let historyMode = event.target.getElementById("historyMode");
historyMode.value = "custom";
historyMode.doCommand();
Services.ww.registerNotification(cookiesWindowObserver);
event.target.getElementById("cookieExceptions").doCommand();
}, false);
}
function cookiesWindowObserver(subject, topic, data) {
if (topic != "domwindowopened")
return;
Services.ww.unregisterNotification(this);
let win = subject.QueryInterface(Ci.nsIDOMEventTarget);
win.addEventListener("load", function(event) {
SimpleTest.executeSoon(function() windowLoad(event, win, dialog));
}, false);
}
Services.ww.registerNotification(prefWindowObserver);
let dialog = openDialog("chrome://browser/content/preferences/preferences.xul",
"Preferences", "chrome,titlebar,toolbar,centerscreen,dialog=no",
"panePrivacy");
testRunner.runTests();
}
function windowLoad(event, win, dialog) {
let doc = event.target;
let tree = doc.getElementById("permissionsTree");
let statusCol = tree.treeBoxObject.columns.getColumnAt(1);
let url = doc.getElementById("url");
let btnAllow = doc.getElementById("btnAllow");
let btnBlock = doc.getElementById("btnBlock");
let btnRemove = doc.getElementById("removePermission");
let pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
let ioService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
const allowText = win.gPermissionManager._getCapabilityString(
Ci.nsIPermissionManager.ALLOW_ACTION);
const denyText = win.gPermissionManager._getCapabilityString(
Ci.nsIPermissionManager.DENY_ACTION);
const allow = Ci.nsIPermissionManager.ALLOW_ACTION;
const deny = Ci.nsIPermissionManager.DENY_ACTION;
var testRunner = {
is(tree.view.rowCount, 0, "no cookie exceptions");
tests:
[
{
test: function(params) {
params.url.value = "test.com";
params.btnAllow.doCommand();
is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
"permission text should be set correctly");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", host: "test.com", data: "added",
capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
},
{
test: function(params) {
params.url.value = "test.com";
params.btnBlock.doCommand();
is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
"permission should change to deny in UI");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", host: "test.com", data: "changed",
capability: Ci.nsIPermissionManager.DENY_ACTION }],
},
{
test: function(params) {
params.url.value = "test.com";
params.btnAllow.doCommand();
is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
"permission should revert back to allow");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", host: "test.com", data: "changed",
capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
},
{
test: function(params) {
params.url.value = "test.com";
params.btnRemove.doCommand();
is(params.tree.view.rowCount, 0, "exception should be removed");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", host: "test.com", data: "deleted" }],
},
{
test: function(params) {
let uri = params.ioService.newURI("http://test.com", null, null);
params.pm.add(uri, "popup", Ci.nsIPermissionManager.DENY_ACTION);
is(params.tree.view.rowCount, 0, "adding unrelated permission should not change display");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "popup", host: "test.com", data: "added",
capability: Ci.nsIPermissionManager.DENY_ACTION }],
cleanUp: function(params) {
params.pm.remove("test.com", "popup");
},
},
],
let tests = [
{
test: function() {
url.value = "test.com";
btnAllow.doCommand();
is(tree.view.rowCount, 1, "added exception shows up in treeview");
is(tree.view.getCellText(0, statusCol), allowText,
"permission text should be set correctly");
},
observances: [{ type: "cookie", host: "test.com", data: "added",
capability: allow }]
},
{
test: function() {
url.value = "test.com";
btnBlock.doCommand();
is(tree.view.getCellText(0, statusCol), denyText,
"permission should change to deny in UI");
},
observances: [{ type: "cookie", host: "test.com", data: "changed",
capability: deny }],
},
{
test: function() {
url.value = "test.com";
btnAllow.doCommand();
is(tree.view.getCellText(0, statusCol), allowText,
"permission should revert back to allow");
},
observances: [{ type: "cookie", host: "test.com", data: "changed",
capability: allow }],
},
{
test: function() {
url.value = "test.com";
btnRemove.doCommand();
is(tree.view.rowCount, 0, "exception should be removed");
},
observances: [{ type: "cookie", host: "test.com", data: "deleted" }],
},
{
test: function() {
let uri = ioService.newURI("http://test.com", null, null);
pm.add(uri, "popup", Ci.nsIPermissionManager.DENY_ACTION);
is(tree.view.rowCount, 0, "adding unrelated permission should not change display");
},
observances: [{ type: "popup", host: "test.com", data: "added",
capability: deny }],
cleanUp: function() {
pm.remove("test.com", "popup");
},
},
{
test: function() {
url.value = "test.com";
btnAllow.doCommand();
pm.remove("test.com", "cookie");
is(tree.view.rowCount, 0, "display should update when cookie permission is deleted");
},
observances: [{ type: "cookie", host: "test.com", data: "added",
capability: allow },
{ type: "cookie", host: "test.com", data: "deleted" }]
},
];
_currentTest: -1,
let permObserver = {
observe: function(aSubject, aTopic, aData) {
if (aTopic != "perm-changed")
return;
runTests: function() {
this._currentTest++;
if (tests[currentTest].observances.length == 0) {
// Should fail here as we are not expecting a notification, but we don't.
// See bug 1063410.
return;
info("Running test #" + (this._currentTest + 1) + "\n");
let that = this;
let p = this.runCurrentTest();
p.then(function() {
if (that._currentTest == that.tests.length - 1) {
finish();
}
let permission = aSubject.QueryInterface(Ci.nsIPermission);
let expected = tests[currentTest].observances.shift();
is(aData, expected.data, "type of message should be the same");
for each (let prop in ["type", "host", "capability"]) {
if (expected[prop])
is(permission[prop], expected[prop],
"property: \"" + prop + "\" should be equal");
else {
that.runTests();
}
});
},
if (tests[currentTest].observances.length == 0) {
SimpleTest.executeSoon(function() {
if (tests[currentTest].cleanUp)
tests[currentTest].cleanUp();
runCurrentTest: function() {
return new Promise(function(resolve, reject) {
runNextTest();
});
}
},
};
let helperFunctions = {
let os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
prefWindowObserver: function(subject, topic, data) {
if (topic != "domwindowopened")
return;
os.addObserver(permObserver, "perm-changed", false);
Services.ww.unregisterNotification(helperFunctions.prefWindowObserver);
var currentTest = -1;
function runNextTest() {
currentTest++;
if (currentTest == tests.length) {
os.removeObserver(permObserver, "perm-changed");
win.close();
dialog.close();
finish();
return;
}
let win = subject.QueryInterface(Ci.nsIDOMEventTarget);
info("Running test #" + (currentTest + 1) + "\n");
tests[currentTest].test();
if (!tests[currentTest].observances)
runNextTest();
}
win.addEventListener("load", function(event) {
let historyMode = event.target.getElementById("historyMode");
historyMode.value = "custom";
historyMode.doCommand();
Services.ww.registerNotification(helperFunctions.cookiesWindowObserver);
event.target.getElementById("cookieExceptions").doCommand();
}, false);
},
runNextTest();
}
cookiesWindowObserver: function(subject, topic, data) {
if (topic != "domwindowopened")
return;
Services.ww.unregisterNotification(helperFunctions.cookiesWindowObserver);
let win = subject.QueryInterface(Ci.nsIDOMEventTarget);
win.addEventListener("load", function(event) {
SimpleTest.executeSoon(function() helperFunctions.windowLoad(event, win));
}, false);
},
windowLoad: function(event, win) {
let params = {
doc: event.target,
tree: event.target.getElementById("permissionsTree"),
statusCol: event.target.getElementById("permissionsTree").treeBoxObject.columns.getColumnAt(1),
url: event.target.getElementById("url"),
btnAllow: event.target.getElementById("btnAllow"),
btnBlock: event.target.getElementById("btnBlock"),
btnApplyChanges: event.target.getElementById("btnApplyChanges"),
btnRemove: event.target.getElementById("removePermission"),
pm: Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager),
ioService: Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService),
allowText: win.gPermissionManager._getCapabilityString(
Ci.nsIPermissionManager.ALLOW_ACTION),
denyText: win.gPermissionManager._getCapabilityString(
Ci.nsIPermissionManager.DENY_ACTION),
allow: Ci.nsIPermissionManager.ALLOW_ACTION,
deny: Ci.nsIPermissionManager.DENY_ACTION,
};
let permObserver = {
observe: function(aSubject, aTopic, aData) {
if (aTopic != "perm-changed")
return;
if (testRunner.tests[testRunner._currentTest].observances.length == 0) {
// Should fail here as we are not expecting a notification, but we don't.
// See bug 1063410.
return;
}
let permission = aSubject.QueryInterface(Ci.nsIPermission);
let expected = testRunner.tests[testRunner._currentTest].observances.shift();
is(aData, expected.data, "type of message should be the same");
for each (let prop in ["type", "host", "capability"]) {
if (expected[prop])
is(permission[prop], expected[prop],
"property: \"" + prop + "\" should be equal");
}
os.removeObserver(permObserver, "perm-changed");
if (testRunner.tests[testRunner._currentTest].cleanup) {
testRunner.tests[testRunner._currentTest].cleanup();
}
testRunner.dialog.close(params);
win.close();
resolve();
},
};
let os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
os.addObserver(permObserver, "perm-changed", false);
if (testRunner._currentTest == 0) {
is(params.tree.view.rowCount, 0, "no cookie exceptions");
}
testRunner.tests[testRunner._currentTest].test(params);
},
};
Services.ww.registerNotification(helperFunctions.prefWindowObserver);
testRunner.dialog = openDialog("chrome://browser/content/preferences/preferences.xul",
"Preferences", "chrome,titlebar,toolbar,centerscreen,dialog=no",
"panePrivacy");
});
},
};

View File

@ -3,7 +3,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY window.title "Exceptions">
<!ENTITY window.width "36em">
<!ENTITY window.width "45em">
<!ENTITY treehead.sitename.label "Site">
<!ENTITY treehead.status.label "Status">
@ -21,6 +21,8 @@
<!ENTITY allow.accesskey "A">
<!ENTITY windowClose.key "w">
<!ENTITY button.close.label "Close">
<!ENTITY button.close.accesskey "C">
<!ENTITY button.cancel.label "Cancel">
<!ENTITY button.cancel.accesskey "C">
<!ENTITY button.ok.label "Save Changes">
<!ENTITY button.ok.accesskey "S">