summaryrefslogtreecommitdiffstats
path: root/dom/permission
diff options
context:
space:
mode:
Diffstat (limited to 'dom/permission')
-rw-r--r--dom/permission/PermissionObserver.cpp131
-rw-r--r--dom/permission/PermissionObserver.h49
-rw-r--r--dom/permission/PermissionPromptService.js95
-rw-r--r--dom/permission/PermissionPromptService.manifest2
-rw-r--r--dom/permission/PermissionSettings.js136
-rw-r--r--dom/permission/PermissionSettings.jsm200
-rw-r--r--dom/permission/PermissionSettings.manifest2
-rw-r--r--dom/permission/PermissionStatus.cpp129
-rw-r--r--dom/permission/PermissionStatus.h58
-rw-r--r--dom/permission/PermissionUtils.cpp62
-rw-r--r--dom/permission/PermissionUtils.h25
-rw-r--r--dom/permission/Permissions.cpp181
-rw-r--r--dom/permission/Permissions.h56
-rw-r--r--dom/permission/moz.build39
-rw-r--r--dom/permission/tests/file_empty.html2
-rw-r--r--dom/permission/tests/file_framework.js224
-rw-r--r--dom/permission/tests/file_shim.html99
-rw-r--r--dom/permission/tests/mochitest-time.ini1
-rw-r--r--dom/permission/tests/mochitest.ini31
-rw-r--r--dom/permission/tests/test_browser.html49
-rw-r--r--dom/permission/tests/test_embed-apps.html61
-rw-r--r--dom/permission/tests/test_idle.html46
-rw-r--r--dom/permission/tests/test_input-manage.html69
-rw-r--r--dom/permission/tests/test_keyboard.html51
-rw-r--r--dom/permission/tests/test_networkstats-manage.html31
-rw-r--r--dom/permission/tests/test_permissions.html31
-rw-r--r--dom/permission/tests/test_permissions_api.html206
-rw-r--r--dom/permission/tests/test_power.html30
-rw-r--r--dom/permission/tests/test_presentation-device-manage.html38
-rw-r--r--dom/permission/tests/test_systemXHR.html38
-rw-r--r--dom/permission/tests/test_tcp-socket.html52
-rw-r--r--dom/permission/tests/test_time.html30
-rw-r--r--dom/permission/tests/test_udp-socket.html47
-rw-r--r--dom/permission/tests/test_wifi-manage.html41
-rw-r--r--dom/permission/tests/unit/test_bug808734.js73
-rw-r--r--dom/permission/tests/unit/xpcshell.ini5
36 files changed, 2420 insertions, 0 deletions
diff --git a/dom/permission/PermissionObserver.cpp b/dom/permission/PermissionObserver.cpp
new file mode 100644
index 000000000..beda385e7
--- /dev/null
+++ b/dom/permission/PermissionObserver.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "PermissionObserver.h"
+
+#include "mozilla/dom/PermissionStatus.h"
+#include "mozilla/Services.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIObserverService.h"
+#include "nsIPermission.h"
+#include "PermissionUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+PermissionObserver* gInstance = nullptr;
+} // namespace
+
+NS_IMPL_ISUPPORTS(PermissionObserver,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+PermissionObserver::PermissionObserver()
+{
+ MOZ_ASSERT(!gInstance);
+}
+
+PermissionObserver::~PermissionObserver()
+{
+ MOZ_ASSERT(mSinks.IsEmpty());
+ MOZ_ASSERT(gInstance == this);
+
+ gInstance = nullptr;
+}
+
+/* static */ already_AddRefed<PermissionObserver>
+PermissionObserver::GetInstance()
+{
+ RefPtr<PermissionObserver> instance = gInstance;
+ if (!instance) {
+ instance = new PermissionObserver();
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return nullptr;
+ }
+
+ nsresult rv = obs->AddObserver(instance, "perm-changed", true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ gInstance = instance;
+ }
+
+ return instance.forget();
+}
+
+void
+PermissionObserver::AddSink(PermissionStatus* aSink)
+{
+ MOZ_ASSERT(aSink);
+ MOZ_ASSERT(!mSinks.Contains(aSink));
+
+ mSinks.AppendElement(aSink);
+}
+
+void
+PermissionObserver::RemoveSink(PermissionStatus* aSink)
+{
+ MOZ_ASSERT(aSink);
+ MOZ_ASSERT(mSinks.Contains(aSink));
+
+ mSinks.RemoveElement(aSink);
+}
+
+void
+PermissionObserver::Notify(PermissionName aName, nsIPrincipal& aPrincipal)
+{
+ for (auto* sink : mSinks) {
+ if (sink->mName != aName) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPrincipal> sinkPrincipal = sink->GetPrincipal();
+ if (NS_WARN_IF(!sinkPrincipal) || !aPrincipal.Equals(sinkPrincipal)) {
+ continue;
+ }
+
+ sink->PermissionChanged();
+ }
+}
+
+NS_IMETHODIMP
+PermissionObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(!strcmp(aTopic, "perm-changed"));
+
+ if (mSinks.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPermission> perm = do_QueryInterface(aSubject);
+ if (!perm) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ perm->GetPrincipal(getter_AddRefs(principal));
+ if (!principal) {
+ return NS_OK;
+ }
+
+ nsAutoCString type;
+ perm->GetType(type);
+ Maybe<PermissionName> permission = TypeToPermissionName(type.get());
+ if (permission) {
+ Notify(permission.value(), *principal);
+ }
+
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/permission/PermissionObserver.h b/dom/permission/PermissionObserver.h
new file mode 100644
index 000000000..e92452b8d
--- /dev/null
+++ b/dom/permission/PermissionObserver.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_PermissionObserver_h_
+#define mozilla_dom_PermissionObserver_h_
+
+#include "mozilla/dom/PermissionsBinding.h"
+
+#include "nsIObserver.h"
+#include "nsIPrincipal.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace dom {
+
+class PermissionStatus;
+
+// Singleton that watches for perm-changed notifications in order to notify
+// PermissionStatus objects.
+class PermissionObserver final
+ : public nsIObserver
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static already_AddRefed<PermissionObserver> GetInstance();
+
+ void AddSink(PermissionStatus* aObs);
+ void RemoveSink(PermissionStatus* aObs);
+
+private:
+ PermissionObserver();
+ virtual ~PermissionObserver();
+
+ void Notify(PermissionName aName, nsIPrincipal& aPrincipal);
+
+ nsTArray<PermissionStatus*> mSinks;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/permission/PermissionPromptService.js b/dom/permission/PermissionPromptService.js
new file mode 100644
index 000000000..47418d5de
--- /dev/null
+++ b/dom/permission/PermissionPromptService.js
@@ -0,0 +1,95 @@
+/* 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";
+
+/* static functions */
+var DEBUG = 0;
+var debug;
+if (DEBUG) {
+ debug = function (s) { dump("-*- PermissionPromptService: " + s + "\n"); };
+}
+else {
+ debug = function (s) {};
+}
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
+
+const PERMISSIONPROMPTSERVICE_CONTRACTID = "@mozilla.org/permission-prompt-service;1";
+const PERMISSIONPROMPTSERVICE_CID = Components.ID("{e5f953b3-a6ca-444e-a88d-cdc81383741c}");
+const permissionPromptService = Ci.nsIPermissionPromptService;
+
+var permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
+var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+
+function makePrompt()
+{
+ return Cc["@mozilla.org/content-permission/prompt;1"].createInstance(Ci.nsIContentPermissionPrompt);
+}
+
+function PermissionPromptService()
+{
+ debug("Constructor");
+}
+
+PermissionPromptService.prototype = {
+
+ classID : PERMISSIONPROMPTSERVICE_CID,
+
+ QueryInterface : XPCOMUtils.generateQI([permissionPromptService, Ci.nsIObserver]),
+
+ classInfo : XPCOMUtils.generateCI({classID: PERMISSIONPROMPTSERVICE_CID,
+ contractID: PERMISSIONPROMPTSERVICE_CONTRACTID,
+ classDescription: "PermissionPromptService",
+ interfaces: [permissionPromptService]
+ }),
+ /**
+ * getPermission
+ * Ask for permission for an API, device, etc.
+ * @param nsIContentPermissionRequest aRequest
+ * @returns void
+ **/
+ getPermission: function PS_getPermission(aRequest)
+ {
+ if (!(aRequest instanceof Ci.nsIContentPermissionRequest)) {
+ throw new Error("PermissionService.getPermission: "
+ + "2nd argument must be type 'nsIContentPermissionRequest'");
+ }
+
+ // Only allow exactly one permission request here.
+ let types = aRequest.types.QueryInterface(Ci.nsIArray);
+ if (types.length != 1) {
+ aRequest.cancel();
+ return;
+ }
+ let reqType = types.queryElementAt(0, Ci.nsIContentPermissionType);
+
+ let type = reqType.access !== "unused" ? reqType.type + "-" + reqType.access
+ : reqType.type;
+ let perm =
+ permissionManager.testExactPermissionFromPrincipal(aRequest.principal, type);
+
+ switch (perm) {
+ case Ci.nsIPermissionManager.ALLOW_ACTION:
+ aRequest.allow();
+ break;
+ case Ci.nsIPermissionManager.PROMPT_ACTION:
+ makePrompt().prompt(aRequest);
+ break;
+ case Ci.nsIPermissionManager.DENY_ACTION:
+ case Ci.nsIPermissionManager.UNKNOWN_ACTION:
+ default:
+ aRequest.cancel();
+ break;
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PermissionPromptService]);
diff --git a/dom/permission/PermissionPromptService.manifest b/dom/permission/PermissionPromptService.manifest
new file mode 100644
index 000000000..41ec0226c
--- /dev/null
+++ b/dom/permission/PermissionPromptService.manifest
@@ -0,0 +1,2 @@
+component {e5f953b3-a6ca-444e-a88d-cdc81383741c} PermissionPromptService.js
+contract @mozilla.org/permission-prompt-service;1 {e5f953b3-a6ca-444e-a88d-cdc81383741c}
diff --git a/dom/permission/PermissionSettings.js b/dom/permission/PermissionSettings.js
new file mode 100644
index 000000000..ed4f6a3d3
--- /dev/null
+++ b/dom/permission/PermissionSettings.js
@@ -0,0 +1,136 @@
+/* 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 debug(aMsg) {
+ //dump("-*- PermissionSettings.js: " + aMsg + "\n");
+}
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PermissionsTable.jsm");
+
+var cpm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
+
+// PermissionSettings
+
+const PERMISSIONSETTINGS_CONTRACTID = "@mozilla.org/permissionSettings;1";
+const PERMISSIONSETTINGS_CID = Components.ID("{cd2cf7a1-f4c1-487b-8c1b-1a71c7097431}");
+
+function PermissionSettings()
+{
+ debug("Constructor");
+}
+
+XPCOMUtils.defineLazyServiceGetter(this,
+ "appsService",
+ "@mozilla.org/AppsService;1",
+ "nsIAppsService");
+
+PermissionSettings.prototype = {
+ get: function get(aPermName, aManifestURL, aOrigin, aBrowserFlag) {
+ // TODO: Bug 1196644 - Add signPKg parameter into PermissionSettings.js
+ debug("Get called with: " + aPermName + ", " + aManifestURL + ", " + aOrigin + ", " + aBrowserFlag);
+ let uri = Services.io.newURI(aOrigin, null, null);
+ let appID = appsService.getAppLocalIdByManifestURL(aManifestURL);
+ let principal =
+ Services.scriptSecurityManager.createCodebasePrincipal(uri,
+ {appId: appID,
+ inIsolatedMozBrowser: aBrowserFlag});
+ let result = Services.perms.testExactPermanentPermission(principal, aPermName);
+
+ switch (result)
+ {
+ case Ci.nsIPermissionManager.UNKNOWN_ACTION:
+ return "unknown";
+ case Ci.nsIPermissionManager.ALLOW_ACTION:
+ return "allow";
+ case Ci.nsIPermissionManager.DENY_ACTION:
+ return "deny";
+ case Ci.nsIPermissionManager.PROMPT_ACTION:
+ return "prompt";
+ default:
+ dump("Unsupported PermissionSettings Action!\n");
+ return "unknown";
+ }
+ },
+
+ isExplicit: function isExplicit(aPermName, aManifestURL, aOrigin,
+ aBrowserFlag) {
+ // TODO: Bug 1196644 - Add signPKg parameter into PermissionSettings.js
+ debug("isExplicit: " + aPermName + ", " + aManifestURL + ", " + aOrigin);
+ let uri = Services.io.newURI(aOrigin, null, null);
+ let app = appsService.getAppByManifestURL(aManifestURL);
+ let principal = Services.scriptSecurityManager
+ .createCodebasePrincipal(uri, {appId: app.localId, inIsolatedMozBrowser: aBrowserFlag});
+
+ return isExplicitInPermissionsTable(aPermName,
+ principal.appStatus);
+ },
+
+ set: function set(aPermName, aPermValue, aManifestURL, aOrigin,
+ aBrowserFlag) {
+ debug("Set called with: " + aPermName + ", " + aManifestURL + ", " +
+ aOrigin + ", " + aPermValue + ", " + aBrowserFlag);
+ let currentPermValue = this.get(aPermName, aManifestURL, aOrigin,
+ aBrowserFlag);
+ let action;
+ // Check for invalid calls so that we throw an exception rather than get
+ // killed by parent process
+ if (currentPermValue === "unknown" ||
+ aPermValue === "unknown" ||
+ !this.isExplicit(aPermName, aManifestURL, aOrigin, aBrowserFlag)) {
+ let errorMsg = "PermissionSettings.js: '" + aPermName + "'" +
+ " is an implicit permission for '" + aManifestURL +
+ "' or the permission isn't set";
+ Cu.reportError(errorMsg);
+ throw new Components.Exception(errorMsg);
+ }
+
+ cpm.sendSyncMessage("PermissionSettings:AddPermission", {
+ type: aPermName,
+ origin: aOrigin,
+ manifestURL: aManifestURL,
+ value: aPermValue,
+ browserFlag: aBrowserFlag
+ });
+ },
+
+ remove: function remove(aPermName, aManifestURL, aOrigin) {
+ // TODO: Bug 1196644 - Add signPKg parameter into PermissionSettings.js
+ let uri = Services.io.newURI(aOrigin, null, null);
+ let appID = appsService.getAppLocalIdByManifestURL(aManifestURL);
+ let principal =
+ Services.scriptSecurityManager.createCodebasePrincipal(uri,
+ {appId: appID,
+ inIsolatedMozBrowser: true});
+
+ if (principal.appStatus !== Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED) {
+ let errorMsg = "PermissionSettings.js: '" + aOrigin + "'" +
+ " is installed or permission is implicit, cannot remove '" +
+ aPermName + "'.";
+ Cu.reportError(errorMsg);
+ throw new Components.Exception(errorMsg);
+ }
+
+ // PermissionSettings.jsm handles delete when value is "unknown"
+ cpm.sendSyncMessage("PermissionSettings:AddPermission", {
+ type: aPermName,
+ origin: aOrigin,
+ manifestURL: aManifestURL,
+ value: "unknown",
+ browserFlag: true
+ });
+ },
+
+ classID : PERMISSIONSETTINGS_CID,
+ QueryInterface : XPCOMUtils.generateQI([])
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PermissionSettings])
diff --git a/dom/permission/PermissionSettings.jsm b/dom/permission/PermissionSettings.jsm
new file mode 100644
index 000000000..bfd37394d
--- /dev/null
+++ b/dom/permission/PermissionSettings.jsm
@@ -0,0 +1,200 @@
+/* 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 debug(s) {
+ //dump("-*- PermissionSettings Module: " + s + "\n");
+}
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+this.EXPORTED_SYMBOLS = ["PermissionSettingsModule"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PermissionsTable.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+ "@mozilla.org/parentprocessmessagemanager;1",
+ "nsIMessageListenerManager");
+
+XPCOMUtils.defineLazyServiceGetter(this,
+ "appsService",
+ "@mozilla.org/AppsService;1",
+ "nsIAppsService");
+
+this.PermissionSettingsModule = {
+ init: function init() {
+ debug("Init");
+ ppmm.addMessageListener("PermissionSettings:AddPermission", this);
+ Services.obs.addObserver(this, "profile-before-change", false);
+ },
+
+
+ _isChangeAllowed: function(aPrincipal, aPermName, aAction) {
+ // Bug 812289:
+ // Change is allowed from a child process when all of the following
+ // conditions stand true:
+ // * the action isn't "unknown" (so the change isn't a delete) if the app
+ // is installed
+ // * the permission already exists on the database
+ // * the permission is marked as explicit on the permissions table
+ // Note that we *have* to check the first two conditions here because
+ // permissionManager doesn't know if it's being called as a result of
+ // a parent process or child process request. We could check
+ // if the permission is actually explicit (and thus modifiable) or not
+ // on permissionManager also but we currently don't.
+ let perm =
+ Services.perms.testExactPermissionFromPrincipal(aPrincipal,aPermName);
+ let isExplicit = isExplicitInPermissionsTable(aPermName, aPrincipal.appStatus);
+
+ return (aAction === "unknown" &&
+ aPrincipal.appStatus === Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED) ||
+ (aAction !== "unknown" &&
+ (perm !== Ci.nsIPermissionManager.UNKNOWN_ACTION) &&
+ isExplicit);
+ },
+
+ addPermission: function addPermission(aData, aCallbacks) {
+
+ this._internalAddPermission(aData, true, aCallbacks);
+
+ },
+
+
+ _internalAddPermission: function _internalAddPermission(aData, aAllowAllChanges, aCallbacks) {
+ // TODO: Bug 1196644 - Add signPKg parameter into PermissionSettings.jsm.
+ let app;
+ let principal;
+ // Test if app is cached (signed streamable package) or installed via DOMApplicationRegistry
+ if (aData.isCachedPackage) {
+ // If the app is from packaged web app, the origin includes origin attributes already.
+ principal =
+ Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(aData.origin);
+ app = {localId: principal.appId};
+ } else {
+ app = appsService.getAppByManifestURL(aData.manifestURL);
+ let uri = Services.io.newURI(aData.origin, null, null);
+ principal =
+ Services.scriptSecurityManager.createCodebasePrincipal(uri,
+ {appId: app.localId,
+ inIsolatedMozBrowser: aData.browserFlag});
+ }
+
+ let action;
+ switch (aData.value)
+ {
+ case "unknown":
+ action = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+ break;
+ case "allow":
+ action = Ci.nsIPermissionManager.ALLOW_ACTION;
+ break;
+ case "deny":
+ action = Ci.nsIPermissionManager.DENY_ACTION;
+ break;
+ case "prompt":
+ action = Ci.nsIPermissionManager.PROMPT_ACTION;
+ break;
+ default:
+ dump("Unsupported PermisionSettings Action: " + aData.value +"\n");
+ action = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+ }
+
+ if (aAllowAllChanges ||
+ this._isChangeAllowed(principal, aData.type, aData.value)) {
+ debug("add: " + aData.origin + " " + app.localId + " " + action);
+ Services.perms.addFromPrincipal(principal, aData.type, action);
+ return true;
+ } else {
+ debug("add Failure: " + aData.origin + " " + app.localId + " " + action);
+ return false; // This isn't currently used, see comment on setPermission
+ }
+ },
+
+ getPermission: function getPermission(aPermName, aManifestURL, aOrigin, aBrowserFlag, aIsCachedPackage) {
+ // TODO: Bug 1196644 - Add signPKg parameter into PermissionSettings.jsm
+ debug("getPermission: " + aPermName + ", " + aManifestURL + ", " + aOrigin);
+ let principal;
+ // Test if app is cached (signed streamable package) or installed via DOMApplicationRegistry
+ if (aIsCachedPackage) {
+ // If the app is from packaged web app, the origin includes origin attributes already.
+ principal =
+ Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(aOrigin);
+ } else {
+ let uri = Services.io.newURI(aOrigin, null, null);
+ let appID = appsService.getAppLocalIdByManifestURL(aManifestURL);
+ principal =
+ Services.scriptSecurityManager.createCodebasePrincipal(uri,
+ {appId: appID,
+ inIsolatedMozBrowser: aBrowserFlag});
+ }
+ let result = Services.perms.testExactPermissionFromPrincipal(principal, aPermName);
+ switch (result)
+ {
+ case Ci.nsIPermissionManager.UNKNOWN_ACTION:
+ return "unknown";
+ case Ci.nsIPermissionManager.ALLOW_ACTION:
+ return "allow";
+ case Ci.nsIPermissionManager.DENY_ACTION:
+ return "deny";
+ case Ci.nsIPermissionManager.PROMPT_ACTION:
+ return "prompt";
+ default:
+ dump("Unsupported PermissionSettings Action!\n");
+ return "unknown";
+ }
+ },
+
+ removePermission: function removePermission(aPermName, aManifestURL, aOrigin, aBrowserFlag, aIsCachedPackage) {
+ let data = {
+ type: aPermName,
+ origin: aOrigin,
+ manifestURL: aManifestURL,
+ value: "unknown",
+ browserFlag: aBrowserFlag,
+ isCachedPackage: aIsCachedPackage
+ };
+ this._internalAddPermission(data, true);
+ },
+
+ observe: function observe(aSubject, aTopic, aData) {
+ ppmm.removeMessageListener("PermissionSettings:AddPermission", this);
+ Services.obs.removeObserver(this, "profile-before-change");
+ ppmm = null;
+ },
+
+ receiveMessage: function receiveMessage(aMessage) {
+ debug("PermissionSettings::receiveMessage " + aMessage.name);
+ let mm = aMessage.target;
+ let msg = aMessage.data;
+
+ let result;
+ switch (aMessage.name) {
+ case "PermissionSettings:AddPermission":
+ let success = false;
+ let errorMsg =
+ " from a content process with no 'permissions' privileges.";
+ if (mm.assertPermission("permissions")) {
+ success = this._internalAddPermission(msg, false);
+ if (!success) {
+ // Just kill the calling process
+ mm.assertPermission("permissions-modify-implicit");
+ errorMsg = " had an implicit permission change. Child process killed.";
+ }
+ }
+
+ if (!success) {
+ Cu.reportError("PermissionSettings message " + msg.type + errorMsg);
+ return null;
+ }
+ break;
+ }
+ }
+}
+
+PermissionSettingsModule.init();
diff --git a/dom/permission/PermissionSettings.manifest b/dom/permission/PermissionSettings.manifest
new file mode 100644
index 000000000..eb53813ff
--- /dev/null
+++ b/dom/permission/PermissionSettings.manifest
@@ -0,0 +1,2 @@
+component {cd2cf7a1-f4c1-487b-8c1b-1a71c7097431} PermissionSettings.js
+contract @mozilla.org/permissionSettings;1 {cd2cf7a1-f4c1-487b-8c1b-1a71c7097431}
diff --git a/dom/permission/PermissionStatus.cpp b/dom/permission/PermissionStatus.cpp
new file mode 100644
index 000000000..680ece1d0
--- /dev/null
+++ b/dom/permission/PermissionStatus.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/dom/PermissionStatus.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Services.h"
+#include "nsIPermissionManager.h"
+#include "PermissionObserver.h"
+#include "PermissionUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ already_AddRefed<PermissionStatus>
+PermissionStatus::Create(nsPIDOMWindowInner* aWindow,
+ PermissionName aName,
+ ErrorResult& aRv)
+{
+ RefPtr<PermissionStatus> status = new PermissionStatus(aWindow, aName);
+ aRv = status->Init();
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return status.forget();
+}
+
+PermissionStatus::PermissionStatus(nsPIDOMWindowInner* aWindow,
+ PermissionName aName)
+ : DOMEventTargetHelper(aWindow)
+ , mName(aName)
+ , mState(PermissionState::Denied)
+{
+}
+
+nsresult
+PermissionStatus::Init()
+{
+ mObserver = PermissionObserver::GetInstance();
+ if (NS_WARN_IF(!mObserver)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mObserver->AddSink(this);
+
+ nsresult rv = UpdateState();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+PermissionStatus::~PermissionStatus()
+{
+ if (mObserver) {
+ mObserver->RemoveSink(this);
+ }
+}
+
+JSObject*
+PermissionStatus::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PermissionStatusBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult
+PermissionStatus::UpdateState()
+{
+ nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+ if (NS_WARN_IF(!permMgr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ if (NS_WARN_IF(!window)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t action = nsIPermissionManager::DENY_ACTION;
+ nsresult rv = permMgr->TestPermissionFromWindow(window,
+ PermissionNameToType(mName),
+ &action);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mState = ActionToPermissionState(action);
+ return NS_OK;
+}
+
+already_AddRefed<nsIPrincipal>
+PermissionStatus::GetPrincipal() const
+{
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ if (NS_WARN_IF(!window)) {
+ return nullptr;
+ }
+
+ nsIDocument* doc = window->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal =
+ mozilla::BasePrincipal::Cast(doc->NodePrincipal())->CloneStrippingUserContextIdAndFirstPartyDomain();
+ NS_ENSURE_TRUE(principal, nullptr);
+
+ return principal.forget();
+}
+
+void
+PermissionStatus::PermissionChanged()
+{
+ auto oldState = mState;
+ UpdateState();
+ if (mState != oldState) {
+ RefPtr<AsyncEventDispatcher> eventDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("change"), false);
+ eventDispatcher->PostDOMEvent();
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/permission/PermissionStatus.h b/dom/permission/PermissionStatus.h
new file mode 100644
index 000000000..e10f86aaa
--- /dev/null
+++ b/dom/permission/PermissionStatus.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_PermissionStatus_h_
+#define mozilla_dom_PermissionStatus_h_
+
+#include "mozilla/dom/PermissionsBinding.h"
+#include "mozilla/dom/PermissionStatusBinding.h"
+#include "mozilla/DOMEventTargetHelper.h"
+
+namespace mozilla {
+namespace dom {
+
+class PermissionObserver;
+
+class PermissionStatus final
+ : public DOMEventTargetHelper
+{
+ friend class PermissionObserver;
+
+public:
+ static already_AddRefed<PermissionStatus> Create(nsPIDOMWindowInner* aWindow,
+ PermissionName aName,
+ ErrorResult& aRv);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ PermissionState State() const { return mState; }
+
+ IMPL_EVENT_HANDLER(change)
+
+private:
+ ~PermissionStatus();
+
+ PermissionStatus(nsPIDOMWindowInner* aWindow, PermissionName aName);
+
+ nsresult Init();
+
+ nsresult UpdateState();
+
+ already_AddRefed<nsIPrincipal> GetPrincipal() const;
+
+ void PermissionChanged();
+
+ PermissionName mName;
+ PermissionState mState;
+
+ RefPtr<PermissionObserver> mObserver;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_permissionstatus_h_
diff --git a/dom/permission/PermissionUtils.cpp b/dom/permission/PermissionUtils.cpp
new file mode 100644
index 000000000..1bb1c1c0d
--- /dev/null
+++ b/dom/permission/PermissionUtils.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "PermissionUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+const char* kPermissionTypes[] = {
+ "geo",
+ "desktop-notification",
+ // Alias `push` to `desktop-notification`.
+ "desktop-notification"
+};
+
+// `-1` for the last null entry.
+const size_t kPermissionNameCount =
+ MOZ_ARRAY_LENGTH(PermissionNameValues::strings) - 1;
+
+static_assert(MOZ_ARRAY_LENGTH(kPermissionTypes) == kPermissionNameCount,
+ "kPermissionTypes and PermissionName count should match");
+
+const char*
+PermissionNameToType(PermissionName aName)
+{
+ MOZ_ASSERT((size_t)aName < ArrayLength(kPermissionTypes));
+ return kPermissionTypes[static_cast<size_t>(aName)];
+}
+
+Maybe<PermissionName>
+TypeToPermissionName(const char* aType)
+{
+ for (size_t i = 0; i < ArrayLength(kPermissionTypes); ++i) {
+ if (!strcmp(aType, kPermissionTypes[i])) {
+ return Some(static_cast<PermissionName>(i));
+ }
+ }
+
+ return Nothing();
+}
+
+PermissionState
+ActionToPermissionState(uint32_t aAction)
+{
+ switch (aAction) {
+ case nsIPermissionManager::ALLOW_ACTION:
+ return PermissionState::Granted;
+
+ case nsIPermissionManager::DENY_ACTION:
+ return PermissionState::Denied;
+
+ default:
+ case nsIPermissionManager::PROMPT_ACTION:
+ return PermissionState::Prompt;
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/permission/PermissionUtils.h b/dom/permission/PermissionUtils.h
new file mode 100644
index 000000000..34bf7573d
--- /dev/null
+++ b/dom/permission/PermissionUtils.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_PermissionUtils_h_
+#define mozilla_dom_PermissionUtils_h_
+
+#include "mozilla/dom/PermissionsBinding.h"
+#include "mozilla/dom/PermissionStatusBinding.h"
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+namespace dom {
+
+const char* PermissionNameToType(PermissionName aName);
+Maybe<PermissionName> TypeToPermissionName(const char* aType);
+
+PermissionState ActionToPermissionState(uint32_t aAction);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/permission/Permissions.cpp b/dom/permission/Permissions.cpp
new file mode 100644
index 000000000..ddbfc5ffe
--- /dev/null
+++ b/dom/permission/Permissions.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/dom/Permissions.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/PermissionsBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/Services.h"
+#include "nsIPermissionManager.h"
+#include "PermissionUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Permissions)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Permissions)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Permissions)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Permissions, mWindow)
+
+Permissions::Permissions(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow)
+{
+}
+
+Permissions::~Permissions()
+{
+}
+
+JSObject*
+Permissions::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PermissionsBinding::Wrap(aCx, this, aGivenProto);
+}
+
+namespace {
+
+already_AddRefed<PermissionStatus>
+CreatePermissionStatus(JSContext* aCx,
+ JS::Handle<JSObject*> aPermission,
+ nsPIDOMWindowInner* aWindow,
+ ErrorResult& aRv)
+{
+ PermissionDescriptor permission;
+ JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aPermission));
+ if (NS_WARN_IF(!permission.Init(aCx, value))) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+
+ switch (permission.mName) {
+ case PermissionName::Geolocation:
+ case PermissionName::Notifications:
+ case PermissionName::Push:
+ return PermissionStatus::Create(aWindow, permission.mName, aRv);
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unhandled type");
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return nullptr;
+ }
+}
+
+} // namespace
+
+already_AddRefed<Promise>
+Permissions::Query(JSContext* aCx,
+ JS::Handle<JSObject*> aPermission,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ RefPtr<PermissionStatus> status =
+ CreatePermissionStatus(aCx, aPermission, mWindow, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ MOZ_ASSERT(!status);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(status);
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ promise->MaybeResolve(status);
+ return promise.forget();
+}
+
+/* static */ nsresult
+Permissions::RemovePermission(nsIPrincipal* aPrincipal, const char* aPermissionType)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+ if (NS_WARN_IF(!permMgr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return permMgr->RemoveFromPrincipal(aPrincipal, aPermissionType);
+}
+
+already_AddRefed<Promise>
+Permissions::Revoke(JSContext* aCx,
+ JS::Handle<JSObject*> aPermission,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ PermissionDescriptor permission;
+ JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aPermission));
+ if (NS_WARN_IF(!permission.Init(aCx, value))) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocument> document = mWindow->GetExtantDoc();
+ if (!document) {
+ promise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return promise.forget();
+ }
+
+ nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+ if (NS_WARN_IF(!permMgr)) {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ return promise.forget();
+ }
+
+ const char* permissionType = PermissionNameToType(permission.mName);
+
+ nsresult rv;
+ if (XRE_IsParentProcess()) {
+ rv = RemovePermission(document->NodePrincipal(), permissionType);
+ } else {
+ // Permissions can't be removed from the content process. Send a message
+ // to the parent; `ContentParent::RecvRemovePermission` will call
+ // `RemovePermission`.
+ ContentChild::GetSingleton()->SendRemovePermission(
+ IPC::Principal(document->NodePrincipal()), nsDependentCString(permissionType), &rv);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+
+ RefPtr<PermissionStatus> status =
+ CreatePermissionStatus(aCx, aPermission, mWindow, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ MOZ_ASSERT(!status);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(status);
+ promise->MaybeResolve(status);
+ return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/permission/Permissions.h b/dom/permission/Permissions.h
new file mode 100644
index 000000000..4fcaa5734
--- /dev/null
+++ b/dom/permission/Permissions.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_Permissions_h_
+#define mozilla_dom_Permissions_h_
+
+#include "nsISupports.h"
+#include "nsPIDOMWindow.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class Promise;
+
+class Permissions final
+ : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Permissions)
+
+ explicit Permissions(nsPIDOMWindowInner* aWindow);
+
+ nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<Promise> Query(JSContext* aCx,
+ JS::Handle<JSObject*> aPermission,
+ ErrorResult& aRv);
+
+ static nsresult RemovePermission(nsIPrincipal* aPrincipal, const char* aPermissionType);
+
+ already_AddRefed<Promise> Revoke(JSContext* aCx,
+ JS::Handle<JSObject*> aPermission,
+ ErrorResult& aRv);
+
+private:
+ ~Permissions();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_permissions_h_
diff --git a/dom/permission/moz.build b/dom/permission/moz.build
new file mode 100644
index 000000000..0be8a7535
--- /dev/null
+++ b/dom/permission/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ 'Permissions.h',
+ 'PermissionStatus.h',
+]
+
+UNIFIED_SOURCES += [
+ 'PermissionObserver.cpp',
+ 'Permissions.cpp',
+ 'PermissionStatus.cpp',
+ 'PermissionUtils.cpp',
+]
+
+EXTRA_COMPONENTS += [
+ 'PermissionPromptService.js',
+ 'PermissionPromptService.manifest',
+ 'PermissionSettings.js',
+ 'PermissionSettings.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'PermissionSettings.jsm',
+]
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+
+if CONFIG['MOZ_TIME_MANAGER']:
+ MOCHITEST_MANIFESTS += ['tests/mochitest-time.ini']
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
diff --git a/dom/permission/tests/file_empty.html b/dom/permission/tests/file_empty.html
new file mode 100644
index 000000000..bc98b4d2e
--- /dev/null
+++ b/dom/permission/tests/file_empty.html
@@ -0,0 +1,2 @@
+<h1>I'm just a support file</h1>
+<p>I get loaded to do permission testing.</p> \ No newline at end of file
diff --git a/dom/permission/tests/file_framework.js b/dom/permission/tests/file_framework.js
new file mode 100644
index 000000000..27624f91e
--- /dev/null
+++ b/dom/permission/tests/file_framework.js
@@ -0,0 +1,224 @@
+/** Test for Bug 815105 **/
+/*
+ * gData is an array of object that tests using this framework must pass in
+ * The current tests only pass in a single element array. Each test in
+ * gData is executed by the framework for a given file
+ *
+ * Fields in gData object
+ * perms (required) Array of Strings
+ * list of permissions that this test will need. See
+ * http://dxr.mozilla.org/mozilla-central/source/dom/apps/src/PermissionsTable.jsm
+ * These permissions are added after a sanity check and removed at
+ * test conclusion
+ *
+ * obj (required for default verifier) String
+ * The name of the window.navigator object used for accessing the
+ * WebAPI during the tests
+ *
+ * webidl (required for default verifier) String
+ * idl (required for default verifier) String
+ * Only one of webidl / idl is required
+ * The IDL describing the navigator object. The returned object
+ * during tests /must/ be an instanceof this
+ *
+ * skip (optional) Array of Strings
+ * A list of navigator.userAgent's to skip the second part of tests
+ * on. The tests still verify that you can't get obj on those
+ * platforms without permissions, however it is expected that adding
+ * the permission still won't allow access to those objects
+ *
+ * settings (optional) Array of preference tuples
+ * A list of settings that need to be set before this API is
+ * enabled. Note the settings are set before the sanity check is
+ * performed. If an API gates access only by preferences, then it
+ * will fail the initial test
+ *
+ * verifier (optional) Function
+ * A function used to test whether a WebAPI is accessible or not.
+ * The function takes a success and failure callback which both
+ * accept a msg argument. msg is surfaced up to the top level tests
+ * A default verifier is provided which only attempts to access
+ * the navigator object.
+ *
+ * needParentPerm (optional) Boolean
+ * Whether or not the parent frame requires these permissions as
+ * well. Otherwise the test process may be killed.
+ */
+
+SimpleTest.waitForExplicitFinish();
+var expand = SpecialPowers.Cu.import("resource://gre/modules/PermissionsTable.jsm").expandPermissions;
+const permTable = SpecialPowers.Cu.import("resource://gre/modules/PermissionsTable.jsm").PermissionsTable;
+
+const TEST_DOMAIN = "http://example.org";
+const SHIM_PATH = "/tests/dom/permission/tests/file_shim.html"
+var gContent = document.getElementById('content');
+
+//var gData; defined in external files
+var gCurrentTest = 0;
+var gRemainingTests;
+var pendingTests = {};
+
+function PermTest(aData) {
+ var self = this;
+ var skip = aData.skip || false;
+ this.step = 0;
+ this.data = aData;
+ this.isSkip = skip &&
+ skip.some(function (el) {
+ return navigator.
+ userAgent.toLowerCase().
+ indexOf(el.toLowerCase()) != -1;
+ });
+
+ this.setupParent = false;
+ this.perms = expandPermissions(aData.perm);
+ this.id = gCurrentTest++;
+ this.iframe = null;
+
+ // keep a reference to this for eventhandler
+ pendingTests[this.id] = this;
+
+ this.createFrame = function() {
+ if (self.iframe) {
+ gContent.removeChild(self.iframe);
+ }
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('id', 'testframe' + self.step + self.perms)
+ iframe.setAttribute('remote', true);
+ iframe.src = TEST_DOMAIN + SHIM_PATH;
+ iframe.addEventListener('load', function _iframeLoad() {
+ iframe.removeEventListener('load', _iframeLoad);
+
+ // check permissions are correct
+ var allow = (self.step == 0 ? false : true);
+ self.perms.forEach(function (el) {
+ try {
+ var res = SpecialPowers.hasPermission(el, SpecialPowers.wrap(iframe)
+ .contentDocument);
+ is(res, allow, (allow ? "Has " : "Doesn't have ") + el);
+ } catch(e) {
+ ok(false, "failed " + e);
+ }
+ });
+
+ var msg = {
+ id: self.id,
+ step: self.step++,
+ testdata: self.data,
+ }
+ // start the tests
+ iframe.contentWindow.postMessage(msg, "*");
+ });
+
+ self.iframe = iframe;
+ gContent.appendChild(iframe);
+ }
+
+ this.next = function () {
+ switch(self.step) {
+ case 0:
+ self.createFrame();
+ break;
+ case 1:
+ // add permissions
+ addPermissions(self.perms, SpecialPowers.
+ wrap(self.iframe).
+ contentDocument,
+ self.createFrame.bind(self));
+ break;
+ case 2:
+ if (self.iframe) {
+ gContent.removeChild(self.iframe);
+ }
+ checkFinish();
+ break;
+ default:
+ ok(false, "Should not be reached");
+ break
+ }
+ }
+
+ this.start = function() {
+ // some permissions need parent to have permission as well
+ if (!self.setupParent && self.data.needParentPerm &&
+ !SpecialPowers.isMainProcess()) {
+ self.setupParent = true;
+ addPermissions(self.perms, window.document, self.start.bind(self));
+ } else if (self.data.settings && self.data.settings.length) {
+ SpecialPowers.pushPrefEnv({'set': self.data.settings.slice(0)},
+ self.next.bind(self));
+ } else {
+ self.next();
+ }
+ }
+}
+
+function addPermissions(aPerms, aDoc, aCallback) {
+ var permList = [];
+ aPerms.forEach(function (el) {
+ var obj = {'type': el,
+ 'allow': 1,
+ 'context': aDoc};
+ permList.push(obj);
+ });
+ SpecialPowers.pushPermissions(permList, aCallback);
+}
+
+function expandPermissions(aPerms) {
+ var perms = [];
+ aPerms.forEach(function(el) {
+ var access = permTable[el].access ? "readwrite" : null;
+ var expanded = expand(el, access);
+ for (let i = 0; i < expanded.length; i++) {
+ perms.push(SpecialPowers.unwrap(expanded[i]));
+ }
+ });
+
+ return perms;
+}
+
+function msgHandler(evt) {
+ var data = evt.data;
+ var test = pendingTests[data.id];
+
+ /*
+ * step 2 of tests should fail on
+ * platforms which are skipped
+ */
+ if (test.isSkip && test.step == 2) {
+ todo(data.result, data.msg);
+ } else {
+ ok(data.result, data.msg);
+ }
+
+ if (test) {
+ test.next();
+ } else {
+ ok(false, "Received unknown id " + data.id);
+ checkFinish();
+ }
+}
+
+function checkFinish() {
+ if (--gRemainingTests) {
+ gTestRunner.next();
+ } else {
+ window.removeEventListener('message', msgHandler);
+ SimpleTest.finish();
+ }
+}
+
+function runTest() {
+ gRemainingTests = Object.keys(gData).length;
+
+ for (var test in gData) {
+ var test = new PermTest(gData[test]);
+ test.start();
+ yield undefined;
+ }
+}
+
+var gTestRunner = runTest();
+
+window.addEventListener('load', function() { gTestRunner.next(); }, false);
+window.addEventListener('message', msgHandler, false);
diff --git a/dom/permission/tests/file_shim.html b/dom/permission/tests/file_shim.html
new file mode 100644
index 000000000..7791eba65
--- /dev/null
+++ b/dom/permission/tests/file_shim.html
@@ -0,0 +1,99 @@
+<html>
+<head>
+<script type="application/javascript;version=1.8">
+function TestData(aOpts) {
+ for (var opt in aOpts) {
+ if (aOpts.hasOwnProperty(opt)) {
+ this[opt] = aOpts[opt];
+ }
+ }
+}
+
+TestData.prototype = {
+ getObj: function() {
+ if (!this.obj) {
+ return null;
+ }
+
+ // only one of the 2 should be set
+ if ((this.idl && this.webidl) ||
+ (!this.idl && !this.webidl)) {
+ return null;
+ }
+
+ // split on . to allow nested props
+ var props = this.obj.split(".");
+ var obj = window.navigator;
+
+ for (var i = 0; i < props.length && obj !== undefined; i++) {
+ obj = obj[props[i]];
+ }
+
+ if ((this.webidl && obj instanceof window[this.webidl]) ||
+ (this.idl && obj instanceof SpecialPowers.Ci[this.idl])) {
+ return obj;
+ } else {
+ return null;
+ }
+ },
+
+ // default verifier
+ verifier: function(success, failure) {
+ try {
+ if (this.getObj()) {
+ success(this.perm);
+ } else {
+ failure("Did not receive proper object");
+ }
+ } catch (e) {
+ failure("Received exception!: " + e);
+ }
+ },
+}
+
+function receiveMessage(e) {
+ var src = e.source;
+ var step = e.data.step;
+ var id = e.data.id;
+ var data = new TestData(e.data.testdata);
+ var success, failure;
+
+ function reply(res, msg) {
+ window.removeEventListener("message", receiveMessage, false);
+ src.postMessage({result: res, msg: msg,
+ id: id}, "*");
+ }
+
+ function _success(msg) {
+ reply(true, msg);
+ }
+
+ function _failure(msg) {
+ reply(false, msg);
+ }
+
+ // flip success and failure around for precheck
+ if (step == 0) {
+ success = _failure;
+ failure = _success;
+ } else {
+ success = _success;
+ failure = _failure;
+ }
+
+ if (data.verifier instanceof Function) {
+ data.verifier(success, failure);
+ } else {
+ // import toSource() function to global
+ eval(data.verifier);
+ verifier.bind(data, success, failure)();
+ }
+}
+
+window.addEventListener("message", receiveMessage, false);
+</script>
+</head>
+<body>
+<div id="content" style="display: none"></div>
+</body>
+</html>
diff --git a/dom/permission/tests/mochitest-time.ini b/dom/permission/tests/mochitest-time.ini
new file mode 100644
index 000000000..24f65b2a4
--- /dev/null
+++ b/dom/permission/tests/mochitest-time.ini
@@ -0,0 +1 @@
+[test_time.html]
diff --git a/dom/permission/tests/mochitest.ini b/dom/permission/tests/mochitest.ini
new file mode 100644
index 000000000..58fd048ca
--- /dev/null
+++ b/dom/permission/tests/mochitest.ini
@@ -0,0 +1,31 @@
+[DEFAULT]
+support-files =
+ file_framework.js
+ file_shim.html
+ file_empty.html
+
+[test_browser.html]
+skip-if = true
+[test_idle.html]
+# skip-if = (toolkit == 'gonk' && debug) #debug-only failure
+skip-if = true
+[test_permissions.html]
+skip-if = true
+[test_permissions_api.html]
+[test_power.html]
+skip-if = true
+[test_presentation-device-manage.html]
+skip-if = true
+[test_systemXHR.html]
+[test_tcp-socket.html]
+skip-if = true
+[test_udp-socket.html]
+skip-if = true
+[test_keyboard.html]
+# skip-if = toolkit == 'android'
+skip-if = true
+[test_input-manage.html]
+# skip-if = toolkit == 'android'
+skip-if = true
+[test_wifi-manage.html]
+skip-if = true
diff --git a/dom/permission/tests/test_browser.html b/dom/permission/tests/test_browser.html
new file mode 100644
index 000000000..0e6f44d58
--- /dev/null
+++ b/dom/permission/tests/test_browser.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 815105 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+function verifier(success, failure) {
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('mozbrowser', 'true');
+ iframe.src = "http://example.org/";
+ iframe.addEventListener('load', function() {
+ iframe.removeEventListener('load', arguments.callee);
+
+ if (iframe.getScreenshot && typeof iframe.getScreenshot == "function") {
+ success("Got mozbrowser");
+ } else {
+ failure("Didn't get mozbrowser") ;
+ }
+ });
+
+ document.getElementById('content').appendChild(iframe);
+}
+
+var gData = [
+ {
+ perm: ["browser"],
+ needParentPerm: true,
+ settings: [["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true]],
+ verifier: verifier.toSource(),
+ }
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_embed-apps.html b/dom/permission/tests/test_embed-apps.html
new file mode 100644
index 000000000..69d4725c7
--- /dev/null
+++ b/dom/permission/tests/test_embed-apps.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 815105 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+/*
+ * embed-apps allows us to create app iframes,
+ * creator must also have the browser permission
+ */
+function verifier(success, failure) {
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('mozbrowser', '');
+ iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
+ iframe.src = "http://example.org/";
+ iframe.addEventListener('load', function() {
+ var appStatus = SpecialPowers.wrap(iframe)
+ .contentDocument.nodePrincipal.appStatus;
+
+ if (appStatus != SpecialPowers.Ci
+ .nsIPrincipal.APP_STATUS_NOT_INSTALLED) {
+ success("Got mozapp");
+ } else {
+ failure("Didn't get mozapp") ;
+ }
+ });
+
+ document.getElementById('content').appendChild(iframe);
+}
+
+var gData = [
+ {
+ perm: ["embed-apps", "browser"],
+ /*
+ * Android doesn't have working apps
+ * Mobile is for B2G which has a weird testing setup
+ * the app iframe gets embed in the test-container iframe
+ * which returns APP_STATUS_NOT_INSTALLED
+ */
+ skip: ["Android", "Mobile"],
+ settings: [["dom.mozBrowserFramesEnabled", true]],
+ verifier: verifier.toSource(),
+ }
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_idle.html b/dom/permission/tests/test_idle.html
new file mode 100644
index 000000000..ef37c3efd
--- /dev/null
+++ b/dom/permission/tests/test_idle.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 815105 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+function verifier(success, failure) {
+ try {
+ var obs = {
+ time: 1,
+ onidle: function() {
+ window.navigator.removeIdleObserver(obs);
+ },
+ }
+
+ // addIdleObserver throws if prinicpal doesn't have the permission
+ window.navigator.addIdleObserver(obs);
+ success("idle");
+ } catch (e) {
+ failure("Got an exception " + e);
+ }
+}
+
+var gData = [
+ {
+ perm: ["idle"],
+ verifier: verifier.toSource(),
+ }
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_input-manage.html b/dom/permission/tests/test_input-manage.html
new file mode 100644
index 000000000..a60a19467
--- /dev/null
+++ b/dom/permission/tests/test_input-manage.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=920977
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 920977 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=920977">Mozilla Bug 920977 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+function verifier(success, failure) {
+ try {
+ if (!this.getObj()) {
+ failure("Did not receive proper object");
+ return;
+ }
+ } catch (e) {
+ failure("Received exception!: " + e);
+ return;
+ }
+
+ try {
+ this.getObj().removeFocus();
+ } catch (e) {
+ failure("Received exception!: " + e);
+ return;
+ }
+
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute("mozbrowser", true);
+ iframe.src = "http://example.org/";
+ iframe.addEventListener("load", function() {
+ iframe.removeEventListener("load", arguments.callee);
+ if (iframe.setInputMethodActive &&
+ typeof iframe.setInputMethodActive == "function") {
+ success("Got setInputMethodActive");
+ } else {
+ failure("Didn't get setInputMethodActive") ;
+ }
+ });
+
+ document.getElementById('content').appendChild(iframe);
+}
+
+var gData = [
+ {
+ perm: ["input-manage", "browser"],
+ needParentPerm: true,
+ obj: "mozInputMethod",
+ webidl: "MozInputMethod",
+ settings: [["dom.mozInputMethod.enabled", true],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true]],
+ verifier: verifier.toSource()
+ }
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_keyboard.html b/dom/permission/tests/test_keyboard.html
new file mode 100644
index 000000000..f7916afc6
--- /dev/null
+++ b/dom/permission/tests/test_keyboard.html
@@ -0,0 +1,51 @@
+-<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=920977
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 920977 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=920977">Mozilla Bug 920977 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+function verifier(success, failure) {
+ try {
+ if (!this.getObj()) {
+ failure("Did not receive proper object");
+ return;
+ }
+ } catch (e) {
+ failure("Received exception!: " + e);
+ return;
+ }
+
+ try {
+ this.getObj().removeFocus();
+ failure("Should receive exception when accessing system only method.!");
+ } catch (e) {
+ success(this.perm);
+ }
+}
+
+var gData = [
+ {
+ perm: ["input"],
+ obj: "mozInputMethod",
+ webidl: "MozInputMethod",
+ settings: [["dom.mozInputMethod.enabled", true]],
+ verifier: verifier.toSource()
+ }
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_networkstats-manage.html b/dom/permission/tests/test_networkstats-manage.html
new file mode 100644
index 000000000..4fdc84771
--- /dev/null
+++ b/dom/permission/tests/test_networkstats-manage.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 815105 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+var gData = [
+ {
+ perm: ["networkstats-manage"],
+ obj: "mozNetworkStats",
+ webidl: "MozNetworkStatsManager",
+ settings: [["dom.mozNetworkStats.enabled", true]],
+ },
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_permissions.html b/dom/permission/tests/test_permissions.html
new file mode 100644
index 000000000..8d629c3da
--- /dev/null
+++ b/dom/permission/tests/test_permissions.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 815105 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+var gData = [
+ {
+ perm: ["permissions"],
+ obj: "mozPermissionSettings",
+ webidl: "PermissionSettings",
+ settings: [["dom.mozPermissionSettings.enabled", true]],
+ },
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_permissions_api.html b/dom/permission/tests/test_permissions_api.html
new file mode 100644
index 000000000..ded74753c
--- /dev/null
+++ b/dom/permission/tests/test_permissions_api.html
@@ -0,0 +1,206 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test for Permissions API</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+ <pre id="test"></pre>
+ <script type="application/javascript;version=1.8">
+ /*globals SpecialPowers, SimpleTest, is, ok, */
+ 'use strict';
+
+ const {
+ UNKNOWN_ACTION,
+ PROMPT_ACTION,
+ ALLOW_ACTION,
+ DENY_ACTION
+ } = SpecialPowers.Ci.nsIPermissionManager;
+
+ SimpleTest.waitForExplicitFinish();
+
+ const PERMISSIONS = [{
+ name: 'geolocation',
+ type: 'geo'
+ }, {
+ name: 'notifications',
+ type: 'desktop-notification'
+ }, {
+ name: 'push',
+ type: 'desktop-notification'
+ }, ];
+
+ const UNSUPPORTED_PERMISSIONS = [
+ 'foobarbaz', // Not in spec, for testing only.
+ 'midi',
+ ];
+
+ // Create a closure, so that tests are run on the correct window object.
+ function createPermissionTester(aWindow) {
+ return {
+ setPermissions(allow) {
+ const permissions = PERMISSIONS.map(({ type }) => {
+ return {
+ type,
+ allow,
+ 'context': aWindow.document
+ };
+ });
+ return new Promise((resolve) => {
+ SpecialPowers.popPermissions(() => {
+ SpecialPowers.pushPermissions(permissions, resolve);
+ });
+ });
+ },
+ revokePermissions() {
+ const promisesToRevoke = PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .revoke({ name })
+ .then(
+ ({ state }) => is(state, 'prompt', `correct state for '${name}'`),
+ () => ok(false, `revoke should not have rejected for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToRevoke);
+ },
+ revokeUnsupportedPermissions() {
+ const promisesToRevoke = UNSUPPORTED_PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .revoke({ name })
+ .then(
+ () => ok(false, `revoke should not have resolved for '${name}'`),
+ error => is(error.name, 'TypeError', `revoke should have thrown TypeError for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToRevoke);
+ },
+ checkPermissions(state) {
+ const promisesToQuery = PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(
+ () => is(state, state, `correct state for '${name}'`),
+ () => ok(false, `query should not have rejected for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToQuery);
+ },
+ checkUnsupportedPermissions() {
+ const promisesToQuery = UNSUPPORTED_PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(
+ () => ok(false, `query should not have resolved for '${name}'`),
+ error => {
+ is(error.name, 'TypeError',
+ `query should have thrown TypeError for '${name}'`);
+ }
+ );
+ });
+ return Promise.all(promisesToQuery);
+ },
+ promiseStateChanged(name, state) {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(status => {
+ return new Promise( resolve => {
+ status.onchange = () => {
+ status.onchange = null;
+ is(status.state, state, `state changed for '${name}'`);
+ resolve();
+ };
+ });
+ },
+ () => ok(false, `query should not have rejected for '${name}'`));
+ },
+ testStatusOnChange() {
+ return new Promise((resolve) => {
+ SpecialPowers.popPermissions(() => {
+ const permission = 'geolocation';
+ const promiseGranted = this.promiseStateChanged(permission, 'granted');
+ this.setPermissions(ALLOW_ACTION);
+ promiseGranted.then(() => {
+ const promisePrompt = this.promiseStateChanged(permission, 'prompt');
+ SpecialPowers.popPermissions();
+ return promisePrompt;
+ }).then(resolve);
+ });
+ });
+ },
+ testInvalidQuery() {
+ return aWindow.navigator.permissions
+ .query({ name: 'invalid' })
+ .then(
+ () => ok(false, 'invalid query should not have resolved'),
+ () => ok(true, 'invalid query should have rejected')
+ );
+ },
+ testInvalidRevoke() {
+ return aWindow.navigator.permissions
+ .revoke({ name: 'invalid' })
+ .then(
+ () => ok(false, 'invalid revoke should not have resolved'),
+ () => ok(true, 'invalid revoke should have rejected')
+ );
+ },
+ };
+ }
+
+ function enablePrefs() {
+ const ops = {
+ 'set': [
+ ['dom.permissions.revoke.enable', true],
+ ],
+ };
+ return SpecialPowers.pushPrefEnv(ops);
+ }
+
+ function createIframe() {
+ return new Promise((resolve) => {
+ const iframe = document.createElement('iframe');
+ iframe.src = 'file_empty.html';
+ iframe.onload = () => resolve(iframe.contentWindow);
+ document.body.appendChild(iframe);
+ });
+ }
+ debugger;
+ window.onload = () => {
+ enablePrefs()
+ .then(createIframe)
+ .then(createPermissionTester)
+ .then((tester) => {
+ return tester
+ .checkUnsupportedPermissions()
+ .then(() => tester.setPermissions(UNKNOWN_ACTION))
+ .then(() => tester.checkPermissions('prompt'))
+ .then(() => tester.setPermissions(PROMPT_ACTION))
+ .then(() => tester.checkPermissions('prompt'))
+ .then(() => tester.setPermissions(ALLOW_ACTION))
+ .then(() => tester.checkPermissions('granted'))
+ .then(() => tester.setPermissions(DENY_ACTION))
+ .then(() => tester.checkPermissions('denied'))
+ .then(() => tester.testStatusOnChange())
+ .then(() => tester.testInvalidQuery())
+ .then(() => tester.revokeUnsupportedPermissions())
+ .then(() => tester.revokePermissions())
+ .then(() => tester.checkPermissions('prompt'))
+ .then(() => tester.testInvalidRevoke());
+ })
+ .then(SimpleTest.finish)
+ .catch((e) => {
+ ok(false, `Unexpected error ${e}`);
+ SimpleTest.finish();
+ });
+ };
+ </script>
+</body>
+
+</html>
diff --git a/dom/permission/tests/test_power.html b/dom/permission/tests/test_power.html
new file mode 100644
index 000000000..51c2e8b53
--- /dev/null
+++ b/dom/permission/tests/test_power.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 815105 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+var gData = [
+ {
+ perm: ["power"],
+ obj: "mozPower",
+ webidl: "MozPowerManager",
+ },
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_presentation-device-manage.html b/dom/permission/tests/test_presentation-device-manage.html
new file mode 100644
index 000000000..67904030a
--- /dev/null
+++ b/dom/permission/tests/test_presentation-device-manage.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1080474
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for presentation-device-manage permission</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1080474">test presentation-device-manage</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+function verifier(success, failure) {
+ if (window.navigator.mozPresentationDeviceInfo) {
+ success("Got mozPresentationDeviceInfo object!");
+ } else {
+ failure("Failed to get mozPresentationDeviceInfo object!");
+ }
+}
+var gData = [
+ {
+ perm: ["presentation-device-manage"],
+ settings: [["dom.presentation.enabled", true]],
+ obj: "mozPresentationDeviceInfo",
+ webidl: "PresentationDeviceInfoManager"
+ }
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_systemXHR.html b/dom/permission/tests/test_systemXHR.html
new file mode 100644
index 000000000..dd16fddfa
--- /dev/null
+++ b/dom/permission/tests/test_systemXHR.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 815105 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+function verifier(success, failure) {
+ var xhr = new XMLHttpRequest({mozSystem: true});
+ if (xhr.mozSystem === true) {
+ success("systemXHR");
+ } else {
+ failure("Couldn't create systemXHR");
+ }
+}
+
+var gData = [
+ {
+ perm: ["systemXHR"],
+ verifier: verifier.toSource(),
+ }
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_tcp-socket.html b/dom/permission/tests/test_tcp-socket.html
new file mode 100644
index 000000000..5a80cff85
--- /dev/null
+++ b/dom/permission/tests/test_tcp-socket.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 815105 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+/* mozTCPSocket only returns null on window init
+ * if the permission isn't set
+ */
+function verifier(success, failure) {
+ try {
+ var conn = navigator.mozTCPSocket.open("http://mochi.test/", 80);
+
+ if (conn) {
+ if (conn instanceof window.TCPSocket) {
+ success("Opened connection");
+ } else {
+ failure("connection didn't match interface");
+ }
+ } else {
+ failure("failed to open connection");
+ }
+ } catch (e) {
+ failure("Got an exception " + e);
+ }
+}
+
+var gData = [
+ {
+ perm: ["tcp-socket"],
+ needParentPerm: true,
+ settings: [["dom.mozTCPSocket.enabled", true]],
+ verifier: verifier.toSource(),
+ }
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_time.html b/dom/permission/tests/test_time.html
new file mode 100644
index 000000000..d7fea7031
--- /dev/null
+++ b/dom/permission/tests/test_time.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 815105 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+var gData = [
+ {
+ perm: ["time"],
+ obj: "mozTime",
+ webidl: "MozTimeManager",
+ },
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_udp-socket.html b/dom/permission/tests/test_udp-socket.html
new file mode 100644
index 000000000..566e7a876
--- /dev/null
+++ b/dom/permission/tests/test_udp-socket.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=745283
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 745283 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=745283">Mozilla Bug 745283 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+function verifier(success, failure) {
+ try {
+ var socket = new UDPSocket();
+
+ if (socket) {
+ success("Opened socket");
+ } else {
+ failure("failed to open socket");
+ }
+ } catch (e) {
+ failure("Got an exception " + e);
+ }
+}
+
+var gData = [
+ {
+ perm: ["udp-socket"],
+ needParentPerm: true,
+ obj: "UDPSocket",
+ webidl: "UDPSocket",
+ settings: [["dom.udpsocket.enabled", true]],
+ verifier: verifier.toSource(),
+ }
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/test_wifi-manage.html b/dom/permission/tests/test_wifi-manage.html
new file mode 100644
index 000000000..94d17b682
--- /dev/null
+++ b/dom/permission/tests/test_wifi-manage.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 815105 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+function verifier(success, failure) {
+ try {
+ this.getObj().enabled;
+ success("Got wifi");
+ } catch (e) {
+ failure("Got an exception " + e);
+ }
+}
+
+var gData = [
+ {
+ perm: ["wifi-manage"],
+ needParentPerm: true,
+ obj: "mozWifiManager",
+ webidl: "MozWifiManager",
+ verifier: verifier.toSource(),
+ },
+]
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/permission/tests/unit/test_bug808734.js b/dom/permission/tests/unit/test_bug808734.js
new file mode 100644
index 000000000..8dfbd9458
--- /dev/null
+++ b/dom/permission/tests/unit/test_bug808734.js
@@ -0,0 +1,73 @@
+var Cu = Components.utils;
+const READWRITE = "readwrite";
+const UNKNOWN = "foobar";
+
+var gData = [
+// test normal expansion
+{
+ permission: "contacts",
+ access: READWRITE,
+ expected: ["contacts-read", "contacts-create",
+ "contacts-write"]
+},
+// test additional expansion and access not having read+create+write
+{
+ permission: "settings",
+ access: READWRITE,
+ expected: ["settings-read", "settings-write",
+ "settings-api-read", "settings-api-write",
+ "indexedDB-chrome-settings-read",
+ "indexedDB-chrome-settings-write"]
+},
+// test unknown access
+{
+ permission: "contacts",
+ access: UNKNOWN,
+ expected: []
+},
+// test unknown permission
+{
+ permission: UNKNOWN,
+ access: READWRITE,
+ expected: []
+}
+];
+
+// check if 2 arrays contain the same elements
+function do_check_set_eq(a1, a2) {
+ do_check_eq(a1.length, a2.length)
+
+ Array.sort(a1);
+ Array.sort(a2);
+
+ for (let i = 0; i < a1.length; ++i) {
+ do_check_eq(a1[i], a2[i])
+ }
+}
+
+function test_substitute_does_not_break_substituted(scope) {
+ const Ci = Components.interfaces;
+
+ // geolocation-noprompt substitutes for geolocation ...
+ do_check_eq(scope.PermissionsTable["geolocation-noprompt"].substitute[0],
+ "geolocation");
+ // ... and sets silent allow ...
+ do_check_eq(scope.PermissionsTable["geolocation-noprompt"].certified,
+ Ci.nsIPermissionManager.ALLOW_ACTION)
+ // ... which works ...
+ do_check_false(scope.isExplicitInPermissionsTable("geolocation-noprompt", Ci.nsIPrincipal.APP_STATUS_CERTIFIED));
+ // ... but does not interfere with geolocation's PROMPT value
+ do_check_true(scope.isExplicitInPermissionsTable("geolocation", Ci.nsIPrincipal.APP_STATUS_CERTIFIED));
+}
+
+function run_test() {
+ var scope = {};
+ Cu.import("resource://gre/modules/PermissionsTable.jsm", scope);
+
+ for (var i = 0; i < gData.length; i++) {
+ var perms = scope.expandPermissions(gData[i].permission,
+ gData[i].access);
+ do_check_set_eq(perms, gData[i].expected);
+ }
+ test_substitute_does_not_break_substituted(scope);
+}
diff --git a/dom/permission/tests/unit/xpcshell.ini b/dom/permission/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..1c296a77c
--- /dev/null
+++ b/dom/permission/tests/unit/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head =
+tail =
+
+[test_bug808734.js]