Bug 1373206 - Create a new dialog for notification settings under Firefox Preferences to match the new spec. r?johannh draft
authorPrathiksha <prathikshaprasadsuman@gmail.com>
Thu, 29 Jun 2017 23:49:35 +0530
changeset 607755 7f3461321b70f8e2e191aae6ada8e6e4c18e8650
parent 602051 f3483af8ecf997453064201c49c48a682c7f3c29
child 637139 346ede18621da8ce4bf4548a48ec406d4fed290e
push id68101
push userbmo:prathikshaprasadsuman@gmail.com
push dateWed, 12 Jul 2017 20:55:52 +0000
reviewersjohannh
bugs1373206
milestone56.0a1
Bug 1373206 - Create a new dialog for notification settings under Firefox Preferences to match the new spec. r?johannh MozReview-Commit-ID: 1DTs11jfNqT
browser/components/preferences/in-content-new/privacy.js
browser/components/preferences/in-content-new/tests/browser.ini
browser/components/preferences/in-content-new/tests/browser_permission_dialog_test.js
browser/components/preferences/jar.mn
browser/components/preferences/sitePermissions.js
browser/components/preferences/sitePermissions.xul
browser/locales/en-US/chrome/browser/preferences/permissions.dtd
browser/locales/en-US/chrome/browser/preferences/preferences.properties
npm-shrinkwrap.json
package.json
--- a/browser/components/preferences/in-content-new/privacy.js
+++ b/browser/components/preferences/in-content-new/privacy.js
@@ -338,18 +338,18 @@ var gPrivacyPane = {
       bundlePrefs.getString("blockliststitle"),
       bundlePrefs.getString("blockliststext"),
     ]);
     appendSearchKeywords("popupPolicyButton", [
       bundlePrefs.getString("popuppermissionstitle"),
       bundlePrefs.getString("popuppermissionstext"),
     ]);
     appendSearchKeywords("notificationsPolicyButton", [
-      bundlePrefs.getString("notificationspermissionstitle"),
-      bundlePrefs.getString("notificationspermissionstext4"),
+      bundlePrefs.getString("notificationspermissionstitle2"),
+      bundlePrefs.getString("notificationspermissionstext5"),
     ]);
     appendSearchKeywords("addonExceptions", [
       bundlePrefs.getString("addons_permissions_title"),
       bundlePrefs.getString("addonspermissionstext"),
     ]);
     appendSearchKeywords("viewSecurityDevicesButton", [
       pkiBundle.getString("enable_fips"),
     ]);
@@ -884,21 +884,21 @@ var gPrivacyPane = {
   // NOTIFICATIONS
 
   /**
    * Displays the notifications exceptions dialog where specific site notification
    * preferences can be set.
    */
   showNotificationExceptions() {
     let bundlePreferences = document.getElementById("bundlePreferences");
-    let params = { permissionType: "desktop-notification" };
-    params.windowTitle = bundlePreferences.getString("notificationspermissionstitle");
-    params.introText = bundlePreferences.getString("notificationspermissionstext4");
+    let params = { permissionType: "desktop-notification"};
+    params.windowTitle = bundlePreferences.getString("notificationspermissionstitle2");
+    params.introText = bundlePreferences.getString("notificationspermissionstext5");
 
-    gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+    gSubDialog.open("chrome://browser/content/preferences/sitePermissions.xul",
                     "resizable=yes", params);
 
     try {
       Services.telemetry
               .getHistogramById("WEB_NOTIFICATION_EXCEPTIONS_OPENED").add();
     } catch (e) {}
   },
 
--- a/browser/components/preferences/in-content-new/tests/browser.ini
+++ b/browser/components/preferences/in-content-new/tests/browser.ini
@@ -52,14 +52,15 @@ skip-if = e10s
 [browser_privacypane_8.js]
 [browser_sanitizeOnShutdown_prefLocked.js]
 [browser_searchsuggestions.js]
 [browser_security.js]
 [browser_siteData.js]
 [browser_siteData2.js]
 [browser_site_login_exceptions.js]
 [browser_subdialogs.js]
+[browser_permission_dialog_test.js]
 support-files =
   subdialog.xul
   subdialog2.xul
 [browser_telemetry.js]
 # Skip this test on Android as FHR and Telemetry are separate systems there.
 skip-if = !healthreport || !telemetry || (os == 'linux' && debug) || (os == 'android')
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content-new/tests/browser_permission_dialog_test.js
@@ -0,0 +1,165 @@
+"use strict";
+
+Components.utils.import("resource:///modules/SitePermissions.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const PERMISSIONS_URL = "chrome://browser/content/preferences/sitePermissions.xul";
+var uri = Services.io.newURI("http://www.example.com");
+var sitePermissionsDialog;
+
+add_task(async function openSitePermissionsDialog() {
+  await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
+
+  let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let doc = content.document;
+    let settingsButton = doc.getElementById("notificationsPolicyButton");
+    settingsButton.click();
+  });
+
+  sitePermissionsDialog = await dialogOpened;
+
+});
+
+add_task(async function addPermission() {
+  let doc = sitePermissionsDialog.document;
+
+  let richlistbox = doc.getElementById("permissionsBox");
+  // First item in the richlistbox contains column headers.
+  Assert.equal(richlistbox.itemCount - 1, 0,
+               "Number of permission items is initially zero");
+
+  // Add notification permission for a website.
+  SitePermissions.set(uri, "desktop-notification", 1);
+
+  // Observe the added permission changes in the dialog UI.
+  Assert.equal(richlistbox.itemCount - 1, 1);
+  Assert.equal(doc.getElementById("label1").value, "http://www.example.com");
+
+});
+
+add_task(async function observePermissionChange() {
+  let doc = sitePermissionsDialog.document;
+
+  Assert.equal(doc.getElementById("state1").label, "Allow",
+                                  "Permission state is 'Allow' initially");
+
+  SitePermissions.set(uri, "desktop-notification", 2);
+
+  // Observe permission change in the dialog UI.
+  Assert.equal(doc.getElementById("state1").label, "Block");
+});
+
+add_task(async function observePermissionDelete() {
+  let doc = sitePermissionsDialog.document;
+
+  let richlistbox = doc.getElementById("permissionsBox");
+  Assert.equal(richlistbox.itemCount - 1, 1,
+               "The box initially contains one permission item");
+
+  SitePermissions.set(uri, "desktop-notification", 0);
+
+  Assert.equal(richlistbox.itemCount - 1, 0);
+});
+
+// Test the UI.
+add_task(async function onPermissionChange() {
+  let doc = sitePermissionsDialog.document;
+
+  // Add a permission
+  SitePermissions.set(uri, "desktop-notification", 1);
+
+  // Change the permission state in the UI.
+  let menulist = doc.getElementById("state1");
+  let menuitem = menulist.childNodes.item(0).childNodes.item(1);
+  menuitem.click();
+
+  Assert.equal(SitePermissions.get(uri, "desktop-notification").state, 1,
+               "Permission state does not change before saving changes");
+
+  let saveChanges = doc.getElementById("btnApplyChanges");
+  saveChanges.click();
+
+  await waitForCondition(() =>
+    SitePermissions.get(uri, "desktop-notification").state == 2);
+
+  gBrowser.removeCurrentTab();
+
+});
+
+add_task(async function openSitePermissionsDialog() {
+  await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
+
+  let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let doc = content.document;
+    let settingsButton = doc.getElementById("notificationsPolicyButton");
+    settingsButton.click();
+  });
+
+  sitePermissionsDialog = await dialogOpened;
+
+});
+
+add_task(async function onPermissionDelete() {
+  let doc = sitePermissionsDialog.document;
+  let richlistbox = doc.getElementById("permissionsBox");
+
+  // Add a permission.
+  SitePermissions.set(uri, "desktop-notification", 1);
+
+  richlistbox.selectItem(richlistbox.getItemAtIndex(1));
+  doc.getElementById("removePermission").click();
+
+  await waitForCondition(() => richlistbox.itemCount - 1 == 0);
+
+  Assert.equal(SitePermissions.get(uri, "desktop-notification").state, 1,
+               "Permission is not deleted before saving changes");
+
+  doc.getElementById("btnApplyChanges").click();
+
+  Assert.equal(SitePermissions.get(uri, "desktop-notification").state, 0);
+
+  gBrowser.removeCurrentTab();
+});
+
+add_task(async function openSitePermissionsDialog() {
+  await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
+
+  let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let doc = content.document;
+    let settingsButton = doc.getElementById("notificationsPolicyButton");
+    settingsButton.click();
+  });
+
+  sitePermissionsDialog = await dialogOpened;
+
+});
+
+add_task(async function onAllPermissionsDelete() {
+  let doc = sitePermissionsDialog.document;
+  let richlistbox = doc.getElementById("permissionsBox");
+
+  // Add two permissions.
+  SitePermissions.set(uri, "desktop-notification", 1);
+  let u = Services.io.newURI("http://www.test.com");
+  SitePermissions.set(u, "desktop-notification", 1);
+
+  doc.getElementById("removeAllPermissions").click();
+  await waitForCondition(() => richlistbox.itemCount - 1 == 0);
+
+  Assert.equal(SitePermissions.get(uri, "desktop-notification").state, 1);
+  Assert.equal(SitePermissions.get(u, "desktop-notification").state, 1,
+               "Permissions are not deleted before saving changes");
+
+  doc.getElementById("btnApplyChanges").click();
+
+  Assert.equal(SitePermissions.get(uri, "desktop-notification").state, 0);
+  Assert.equal(SitePermissions.get(u, "desktop-notification").state, 0);
+
+  gBrowser.removeCurrentTab();
+});
--- a/browser/components/preferences/jar.mn
+++ b/browser/components/preferences/jar.mn
@@ -15,16 +15,18 @@ browser.jar:
     content/browser/preferences/donottrack.xul
 *   content/browser/preferences/fonts.xul
     content/browser/preferences/fonts.js
     content/browser/preferences/handlers.xml
     content/browser/preferences/handlers.css
 *   content/browser/preferences/languages.xul
     content/browser/preferences/languages.js
     content/browser/preferences/permissions.xul
+    content/browser/preferences/sitePermissions.xul
+    content/browser/preferences/sitePermissions.js
     content/browser/preferences/containers.xul
     content/browser/preferences/containers.js
     content/browser/preferences/permissions.js
     content/browser/preferences/sanitize.xul
     content/browser/preferences/sanitize.js
     content/browser/preferences/selectBookmark.xul
     content/browser/preferences/selectBookmark.js
     content/browser/preferences/siteDataSettings.xul
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/sitePermissions.js
@@ -0,0 +1,276 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource:///modules/SitePermissions.jsm");
+
+const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
+
+function Permission(principal, type, capability) {
+  this.principal = principal;
+  this.origin = principal.origin;
+  this.type = type;
+  this.capability = capability;
+}
+
+var gSitePermissionsManager = {
+  _type: "",
+  _permissions: [],
+  _list: [],
+  _isObserving: true,
+  _permissionsToChange: new Map(),
+  _permissionsToDelete: new Map(),
+
+  onLoad() {
+    this._bundle = document.getElementById("bundlePreferences");
+    let params = window.arguments[0];
+    this.init(params);
+  },
+
+  init(params) {
+    if (this._type) {
+      // reusing an open dialog, clear the old observer
+      this.uninit();
+    }
+
+    this._type = params.permissionType;
+
+    let permissionsText = document.getElementById("permissionsText");
+    while (permissionsText.hasChildNodes())
+      permissionsText.firstChild.remove();
+    permissionsText.appendChild(document.createTextNode(params.introText));
+
+    document.title = params.windowTitle;
+
+    let image = document.createElement("image");
+    let urlField = document.getElementById("url");
+    image.setAttribute("src", "chrome://global/skin/icons/search-textbox.svg");
+    urlField.appendChild(image);
+
+    Services.obs.addObserver(this, "perm-changed");
+
+    this._loadPermissions();
+
+    urlField.focus();
+  },
+
+  uninit() {
+    if (this._isObserving) {
+      Services.obs.removeObserver(this, "perm-changed");
+
+      this._isObserving = false;
+    }
+  },
+
+  observe(subject, topic, data) {
+    if (topic !== "perm-changed")
+      return;
+
+    let permission = subject.QueryInterface(Components.interfaces.nsIPermission);
+
+    // Ignore unrelated permission types.
+    if (permission.type != this._type)
+      return;
+
+    if (data == "added") {
+      this._addPermissionToList(permission);
+    } else if (data == "changed") {
+      for (let i = 0; i < this._permissions.length; ++i) {
+        if (permission.matches(this._permissions[i].principal, true)) {
+          this._permissions[i].capability = this._getCapabilityString(permission.capability);
+          this._handleCapabilityChange(i, permission.capability);
+          break;
+        }
+      }
+    } else if (data == "deleted") {
+      this._removePermissionFromList(permission.principal);
+    }
+  },
+
+  _handleCapabilityChange(j, state) {
+    let permissionlistitem = this._list.getItemAtIndex(++j);
+    permissionlistitem.childNodes.item(0).childNodes.item(1).selectedIndex = --state;
+  },
+
+  _getCapabilityString(capability) {
+    let stringKey = null;
+    switch (capability) {
+    case nsIPermissionManager.ALLOW_ACTION:
+      stringKey = "can";
+      break;
+    case nsIPermissionManager.DENY_ACTION:
+      stringKey = "cannot";
+      break;
+    }
+    return this._bundle.getString(stringKey);
+  },
+
+  _addPermissionToList(perm) {
+    if (perm.type == this._type) {
+      let principal = perm.principal;
+      let capabilityString = this._getCapabilityString(perm.capability);
+      let p = new Permission(principal,
+                             perm.type,
+                             capabilityString);
+      this._permissions.push(p);
+      this._createPermissionListItem(p);
+    }
+  },
+
+  _removePermissionFromList(principal) {
+    for (let i = 0; i < this._permissions.length; ++i) {
+      if (this._permissions[i].principal.equals(principal)) {
+        this._permissions.splice(i, 1);
+        this._list.removeItemAt(++i);
+        break;
+      }
+    }
+  },
+
+  _loadPermissions() {
+    this._list = document.getElementById("permissionsBox");
+    this._permissions = [];
+
+    // load permissions into a table
+    let enumerator = Services.perms.enumerator;
+    while (enumerator.hasMoreElements()) {
+      let nextPermission = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
+      this._addPermissionToList(nextPermission);
+    }
+
+    // disable "remove all" button if there are none
+    document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0;
+  },
+
+  _createPermissionListItem(permission) {
+    let richlistitem = document.createElement("richlistitem");
+    let row = document.createElement("hbox");
+    row.setAttribute("flex", "1");
+
+    let hbox = document.createElement("hbox");
+    let website = document.createElement("label");
+    website.setAttribute("value", permission.origin);
+    website.setAttribute("width", "50");
+    website.setAttribute("id", "label" + this._permissions.length);
+    hbox.setAttribute("style", "padding:5px;");
+    hbox.setAttribute("flex", "3");
+    hbox.appendChild(website);
+
+    let menulist = document.createElement("menulist");
+    let menupopup = document.createElement("menupopup");
+    menulist.setAttribute("flex", "1");
+    menulist.setAttribute("width", "50");
+    menulist.setAttribute("id", "state" + this._permissions.length);
+    menulist.appendChild(menupopup);
+    let m1 = document.createElement("menuitem");
+    let m2 = document.createElement("menuitem");
+    m1.setAttribute("label", "Allow");
+    m1.setAttribute("value", "1");
+    m2.setAttribute("label", "Block");
+    m2.setAttribute("value", "2");
+    menupopup.appendChild(m1);
+    menupopup.appendChild(m2);
+    menulist.value = permission.capability == "Allow" ? "1" : "2";
+
+    menulist.addEventListener("select", function() {
+      gSitePermissionsManager.changePermission(permission,
+                              Number(menulist.selectedItem.value));
+    });
+
+    row.appendChild(hbox);
+    row.appendChild(menulist);
+    richlistitem.appendChild(row);
+    this._list.appendChild(richlistitem)
+  },
+
+  onWindowKeyPress(event) {
+    if (event.keyCode == KeyEvent.DOM_VK_ESCAPE)
+      window.close();
+  },
+
+  onPermissionKeyPress(event) {
+    if (event.keyCode == KeyEvent.DOM_VK_DELETE) {
+      this.onPermissionDelete();
+    } else if (AppConstants.platform == "macosx" &&
+               event.keyCode == KeyEvent.DOM_VK_BACK_SPACE) {
+      this.onPermissionDelete();
+      event.preventDefault();
+    }
+  },
+
+  onPermissionDelete() {
+    if (!this._list.itemCount)
+      return;
+
+    let richlistitem = this._list.getSelectedItem(0);
+    let index = this._list.getIndexOfItem(richlistitem);
+
+    let permission = this._permissions[--index];
+    this._removePermissionFromList(permission.principal);
+    this._permissionsToDelete.set(permission.origin, permission);
+
+    document.getElementById("removePermission").disabled = this._permissions.length <= 1;
+    document.getElementById("removeAllPermissions").disabled = this._permissions.length <= 1;
+  },
+
+  onAllPermissionsDelete() {
+    if (!this._list.itemCount)
+      return;
+
+    for (let i = 0; i < this._permissions.length;) {
+      let permission = this._permissions[i];
+      this._removePermissionFromList(permission.principal);
+      this._permissionsToDelete.set(permission.origin, permission);
+    }
+
+    document.getElementById("removePermission").disabled = true;
+    document.getElementById("removeAllPermissions").disabled = true;
+  },
+
+  onPermissionSelect() {
+    let hasSelection = this._list.selectedCount > 0;
+    let hasRows = this._list.itemCount > 0;
+    document.getElementById("removePermission").disabled = !hasRows || !hasSelection;
+    document.getElementById("removeAllPermissions").disabled = !hasRows;
+  },
+
+  changePermission(permission, capability) {
+    let capabilityString = this._getCapabilityString(capability);
+
+    let capabilityExists = false;
+    for (let i = 0; i < this._permissions.length; i++) {
+      if (this._permissions[i].principal.equals(permission.principal)) {
+        capabilityExists = this._permissions[i].capability == capabilityString;
+        if (capabilityExists)
+          return;
+        this._permissions[i].capability = capabilityString;
+        break;
+      }
+    }
+    this._permissionsToChange.set(permission.origin, permission);
+
+    // enable "remove all" button as needed
+    document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0;
+  },
+
+  onApplyChanges() {
+    // 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 p of this._permissionsToChange.values()) {
+      let uri = Services.io.newURI(p.origin);
+      let c = p.capability == "Allow" ? 1 : 2;
+      SitePermissions.set(uri, p.type, c);
+    }
+
+    for (let p of this._permissionsToDelete.values()) {
+      let uri = Services.io.newURI(p.origin);
+      SitePermissions.set(uri, p.type, 0);
+    }
+    window.close();
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/sitePermissions.xul
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/permissions.dtd" >
+
+<window id="SitePermissionsDialog" class="windowDialog"
+        windowtype="Browser:SitePermissions"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        style="width: &window.width;;"
+        onload="gSitePermissionsManager.onLoad();"
+        onunload="gSitePermissionsManager.uninit();"
+        persist="screenX screenY width height"
+        onkeypress="gSitePermissionsManager.onWindowKeyPress(event);">
+
+  <script src="chrome://browser/content/preferences/sitePermissions.js"/>
+
+  <stringbundle id="bundlePreferences"
+                src="chrome://browser/locale/preferences/preferences.properties"/>
+
+  <keyset>
+    <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
+  </keyset>
+
+  <vbox class="contentPane largeDialogContainer" flex="1">
+    <description id="permissionsText" control="url"/>
+    <separator class="thin"/>
+    <hbox align="start">
+      <textbox id="url" flex="1" style="color:gray;" value=" Search Website"/>
+    </hbox>
+    <separator class="thin"/>
+    <richlistbox style="height: 18em;" id="permissionsBox"
+                 hidecolumnpicker="true"
+                 onkeypress="gSitePermissionsManager.onPermissionKeyPress(event);"
+                 onselect="gSitePermissionsManager.onPermissionSelect();">
+    <richlistitem style="height: 35px;">
+      <hbox flex="1">
+        <treecol id="siteCol" label="&treehead.sitename.label;" flex="3"
+                 data-field-name="origin" persist="width" width="50"/>
+        <splitter class="tree-splitter"/>
+        <treecol id="statusCol" label="&treehead.status.label;" flex="1"
+                 data-field-name="capability" persist="width" width="50"/>
+      </hbox>
+    </richlistitem>
+    </richlistbox>
+  </vbox>
+  <vbox>
+    <hbox class="actionButtons" align="left" flex="1">
+      <button id="removePermission" disabled="true"
+              accesskey="&removepermission.accesskey;"
+              icon="remove" label="&removepermission.label;"
+              oncommand="gSitePermissionsManager.onPermissionDelete();"/>
+      <button id="removeAllPermissions"
+              icon="clear" label="&removeallpermissions.label;"
+              accesskey="&removeallpermissions.accesskey;"
+              oncommand="gSitePermissionsManager.onAllPermissionsDelete();"/>
+    </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="gSitePermissionsManager.onApplyChanges();" icon="save"
+              label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
+    </hbox>
+  </vbox>
+</window>
--- a/browser/locales/en-US/chrome/browser/preferences/permissions.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/permissions.dtd
@@ -1,16 +1,16 @@
 <!-- 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/. -->
 
 <!ENTITY window.title                 "Exceptions">
 <!ENTITY window.width                 "45em">
 
-<!ENTITY treehead.sitename.label      "Site">
+<!ENTITY treehead.sitename.label      "Website">
 <!ENTITY treehead.status.label        "Status">
 <!ENTITY removepermission.label       "Remove Site">
 <!ENTITY removepermission.accesskey   "R">
 <!ENTITY removeallpermissions.label   "Remove All Sites">
 <!ENTITY removeallpermissions.accesskey "e">
 <!ENTITY address.label                "Address of website:">
 <!ENTITY address.accesskey            "d">
 <!ENTITY block.label                  "Block">
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -22,18 +22,18 @@ acceptVeryLargeMinimumFont=Keep my chang
 trackingprotectionpermissionstext=You have disabled Tracking Protection on these sites.
 trackingprotectionpermissionstitle=Exceptions - Tracking Protection
 cookiepermissionstext=You can specify which websites are always or never allowed to use cookies.  Type the exact address of the site you want to manage and then click Block, Allow for Session, or Allow.
 cookiepermissionstitle=Exceptions - Cookies
 addonspermissionstext=You can specify which websites are allowed to install add-ons. Type the exact address of the site you want to allow and then click Allow.
 addons_permissions_title=Allowed Sites - Add-ons Installation
 popuppermissionstext=You can specify which websites are allowed to open pop-up windows. Type the exact address of the site you want to allow and then click Allow.
 popuppermissionstitle=Allowed Sites - Pop-ups
-notificationspermissionstext4=Control which websites are always or never allowed to send you notifications. If you remove a site, it will need to request permission again.
-notificationspermissionstitle=Notification Permissions
+notificationspermissionstext5=The following websites have requested to send you notifications. You can specify which websites are allowed to send you notifications.
+notificationspermissionstitle2=Settings - Notification Permissions
 invalidURI=Please enter a valid hostname
 invalidURITitle=Invalid Hostname Entered
 savedLoginsExceptions_title=Exceptions - Saved Logins
 savedLoginsExceptions_desc=Logins for the following sites will not be saved:
 
 #### Block List Manager
 
 blockliststext=You can choose which list Firefox will use to block Web elements that may track your browsing activity.
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -210,30 +210,30 @@
       "version": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz",
       "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw="
     },
     "eslint-plugin-html": {
       "version": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-2.0.3.tgz",
       "integrity": "sha1-fImIOrDIX6XSi2ZqFKTpBqqQuJc="
     },
     "eslint-plugin-mozilla": {
-      "version": "file:tools\\lint\\eslint\\eslint-plugin-mozilla"
+      "version": "file:tools/lint/eslint/eslint-plugin-mozilla"
     },
     "eslint-plugin-react": {
       "version": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz",
       "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=",
       "dependencies": {
         "doctrine": {
           "version": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
           "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo="
         }
       }
     },
     "eslint-plugin-spidermonkey-js": {
-      "version": "file:tools\\lint\\eslint\\eslint-plugin-spidermonkey-js"
+      "version": "file:tools/lint/eslint/eslint-plugin-spidermonkey-js"
     },
     "espree": {
       "version": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz",
       "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q="
     },
     "esprima": {
       "version": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
       "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
--- a/package.json
+++ b/package.json
@@ -2,17 +2,17 @@
   "name": "mozillaeslintsetup",
   "description": "This package file is for setup of ESLint only for editor integration.",
   "repository": {},
   "license": "MPL-2.0",
   "dependencies": {
     "escope": "^3.6.0",
     "eslint": "3.19.0",
     "eslint-plugin-html": "2.0.3",
-    "eslint-plugin-mozilla": "file:tools\\lint\\eslint\\eslint-plugin-mozilla",
+    "eslint-plugin-mozilla": "file:tools/lint/eslint/eslint-plugin-mozilla",
     "eslint-plugin-react": "6.10.3",
-    "eslint-plugin-spidermonkey-js": "file:tools\\lint\\eslint\\eslint-plugin-spidermonkey-js",
+    "eslint-plugin-spidermonkey-js": "file:tools/lint/eslint/eslint-plugin-spidermonkey-js",
     "espree": "^3.4.0",
     "estraverse": "^4.2.0",
     "ini-parser": "^0.0.2",
     "sax": "^1.2.2"
   }
 }