Bug 1202933, Part 1 - Show the origin for XUL notifications. r=MattN,wchen
☠☠ backed out by a1736287f404 ☠ ☠
authorKit Cambridge <kcambridge@mozilla.com>
Mon, 05 Oct 2015 11:49:22 -0700
changeset 266194 1763e148406574a02a516e199a820f7a33297e3d
parent 266193 15d9f056935dd28dbd452f166d85b0240b06524f
child 266195 fbcc1bb170e9b8d108ee02b30643b48e76f49db1
push id15532
push usercbook@mozilla.com
push dateTue, 06 Oct 2015 10:23:06 +0000
treeherderfx-team@7603d2860165 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN, wchen
bugs1202933
milestone44.0a1
Bug 1202933, Part 1 - Show the origin for XUL notifications. r=MattN,wchen
toolkit/components/alerts/moz.build
toolkit/components/alerts/nsAlertsService.cpp
toolkit/components/alerts/nsAlertsUtils.cpp
toolkit/components/alerts/nsAlertsUtils.h
toolkit/components/alerts/nsXULAlerts.cpp
toolkit/components/alerts/nsXULAlerts.h
toolkit/components/alerts/resources/content/alert.js
toolkit/components/alerts/resources/content/alert.xul
toolkit/components/alerts/test/mochitest.ini
toolkit/components/alerts/test/test_alerts.html
toolkit/components/alerts/test/test_principal.html
toolkit/locales/en-US/chrome/alerts/alert.properties
toolkit/themes/shared/alert-common.css
--- a/toolkit/components/alerts/moz.build
+++ b/toolkit/components/alerts/moz.build
@@ -7,18 +7,23 @@
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']
 
 XPIDL_SOURCES += [
     'nsIAlertsService.idl',
 ]
 
 XPIDL_MODULE = 'alerts'
 
+EXPORTS += [
+    'nsAlertsUtils.h',
+]
+
 UNIFIED_SOURCES += [
     'nsAlertsService.cpp',
+    'nsAlertsUtils.cpp',
     'nsXULAlerts.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 JAR_MANIFESTS += ['jar.mn']
--- a/toolkit/components/alerts/nsAlertsService.cpp
+++ b/toolkit/components/alerts/nsAlertsService.cpp
@@ -115,17 +115,17 @@ NS_IMETHODIMP nsAlertsService::ShowAlert
     if (aAlertListener)
       aAlertListener->Observe(nullptr, "alertfinished", PromiseFlatString(aAlertCookie).get());
     return NS_OK;
   }
 
   // Use XUL notifications as a fallback if above methods have failed.
   rv = mXULAlerts.ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable,
                                         aAlertCookie, aAlertListener, aAlertName,
-                                        aBidi, aLang, aInPrivateBrowsing);
+                                        aBidi, aLang, aPrincipal, aInPrivateBrowsing);
   return rv;
 #endif // !MOZ_WIDGET_ANDROID
 }
 
 NS_IMETHODIMP nsAlertsService::CloseAlert(const nsAString& aAlertName,
                                           nsIPrincipal* aPrincipal)
 {
   if (XRE_IsContentProcess()) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/alerts/nsAlertsUtils.cpp
@@ -0,0 +1,74 @@
+/* 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 "nsAlertsUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIURI.h"
+#include "nsXPIDLString.h"
+
+#define ALERTS_BUNDLE "chrome://alerts/locale/alert.properties"
+
+/* static */
+bool
+nsAlertsUtils::IsActionablePrincipal(nsIPrincipal* aPrincipal)
+{
+  return aPrincipal &&
+         !nsContentUtils::IsSystemOrExpandedPrincipal(aPrincipal) &&
+         !aPrincipal->GetIsNullPrincipal();
+}
+
+/* static */
+void
+nsAlertsUtils::GetSource(nsIPrincipal* aPrincipal, nsAString& aSource)
+{
+  nsAutoString hostPort;
+  GetSourceHostPort(aPrincipal, hostPort);
+  if (hostPort.IsEmpty()) {
+    return;
+  }
+  nsCOMPtr<nsIStringBundleService> stringService(
+    mozilla::services::GetStringBundleService());
+  if (!stringService) {
+    return;
+  }
+  nsCOMPtr<nsIStringBundle> alertsBundle;
+  if (NS_WARN_IF(NS_FAILED(stringService->CreateBundle(ALERTS_BUNDLE,
+      getter_AddRefs(alertsBundle))))) {
+    return;
+  }
+  const char16_t* params[1] = { hostPort.get() };
+  nsXPIDLString result;
+  if (NS_WARN_IF(NS_FAILED(
+      alertsBundle->FormatStringFromName(MOZ_UTF16("source.label"), params, 1,
+      getter_Copies(result))))) {
+    return;
+  }
+  aSource = result;
+}
+
+/* static */
+void
+nsAlertsUtils::GetSourceHostPort(nsIPrincipal* aPrincipal,
+                                 nsAString& aHostPort)
+{
+  if (!IsActionablePrincipal(aPrincipal)) {
+    return;
+  }
+  nsCOMPtr<nsIURI> principalURI;
+  if (NS_WARN_IF(NS_FAILED(
+      aPrincipal->GetURI(getter_AddRefs(principalURI))))) {
+    return;
+  }
+  if (!principalURI) {
+    return;
+  }
+  nsAutoCString hostPort;
+  if (NS_WARN_IF(NS_FAILED(principalURI->GetHostPort(hostPort)))) {
+    return;
+  }
+  CopyUTF8toUTF16(hostPort, aHostPort);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/alerts/nsAlertsUtils.h
@@ -0,0 +1,39 @@
+/* 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 nsAlertsUtils_h
+#define nsAlertsUtils_h
+
+#include "nsIPrincipal.h"
+#include "nsString.h"
+
+class nsAlertsUtils final
+{
+private:
+  nsAlertsUtils() = delete;
+
+public:
+  /**
+   * Indicates whether an alert from |aPrincipal| should include the source
+   * string and action buttons. Returns false if |aPrincipal| is |nullptr|, or
+   * a system, expanded, or null principal.
+   */
+  static bool
+  IsActionablePrincipal(nsIPrincipal* aPrincipal);
+
+  /**
+   * Sets |aSource| to the localized notification source string, or an empty
+   * string if |aPrincipal| is not actionable.
+   */
+  static void
+  GetSource(nsIPrincipal* aPrincipal, nsAString& aSource);
+
+  /**
+   * Sets |aHostPort| to the host and port from |aPrincipal|'s URI, or an
+   * empty string if |aPrincipal| is not actionable.
+   */
+  static void
+  GetSourceHostPort(nsIPrincipal* aPrincipal, nsAString& aHostPort);
+};
+#endif /* nsAlertsUtils_h */
--- a/toolkit/components/alerts/nsXULAlerts.cpp
+++ b/toolkit/components/alerts/nsXULAlerts.cpp
@@ -3,16 +3,17 @@
  * 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 "nsXULAlerts.h"
 
 #include "nsAutoPtr.h"
 #include "mozilla/LookAndFeel.h"
 #include "nsIServiceManager.h"
+#include "nsAlertsUtils.h"
 #include "nsISupportsArray.h"
 #include "nsISupportsPrimitives.h"
 #include "nsPIDOMWindow.h"
 #include "nsIWindowWatcher.h"
 
 using namespace mozilla;
 
 #define ALERT_CHROME_URL "chrome://global/content/alerts/alert.xul"
@@ -39,17 +40,18 @@ nsXULAlertObserver::Observe(nsISupports*
   return rv;
 }
 
 nsresult
 nsXULAlerts::ShowAlertNotification(const nsAString& aImageUrl, const nsAString& aAlertTitle,
                                    const nsAString& aAlertText, bool aAlertTextClickable,
                                    const nsAString& aAlertCookie, nsIObserver* aAlertListener,
                                    const nsAString& aAlertName, const nsAString& aBidi,
-                                   const nsAString& aLang, bool aInPrivateBrowsing)
+                                   const nsAString& aLang, nsIPrincipal* aPrincipal,
+                                   bool aInPrivateBrowsing)
 {
   nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
 
   nsCOMPtr<nsISupportsArray> argsArray;
   nsresult rv = NS_NewISupportsArray(getter_AddRefs(argsArray));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // create scriptable versions of our strings that we can store in our nsISupportsArray....
@@ -129,16 +131,26 @@ nsXULAlerts::ShowAlertNotification(const
   NS_ENSURE_SUCCESS(rv, rv);
   nsRefPtr<nsXULAlertObserver> alertObserver = new nsXULAlertObserver(this, aAlertName, aAlertListener);
   nsCOMPtr<nsISupports> iSupports(do_QueryInterface(alertObserver));
   ifptr->SetData(iSupports);
   ifptr->SetDataIID(&NS_GET_IID(nsIObserver));
   rv = argsArray->AppendElement(ifptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // The source contains the host and port of the site that sent the
+  // notification. It is empty for system alerts.
+  nsCOMPtr<nsISupportsString> scriptableAlertSource (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+  NS_ENSURE_TRUE(scriptableAlertSource, NS_ERROR_FAILURE);
+  nsAutoString source;
+  nsAlertsUtils::GetSource(aPrincipal, source);
+  scriptableAlertSource->SetData(source);
+  rv = argsArray->AppendElement(scriptableAlertSource);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   nsCOMPtr<nsIDOMWindow> newWindow;
   nsAutoCString features("chrome,dialog=yes,titlebar=no,popup=yes");
   if (aInPrivateBrowsing) {
     features.AppendLiteral(",private");
   }
   rv = wwatch->OpenWindow(0, ALERT_CHROME_URL, "_blank", features.get(),
                           argsArray, getter_AddRefs(newWindow));
   NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/alerts/nsXULAlerts.h
+++ b/toolkit/components/alerts/nsXULAlerts.h
@@ -20,17 +20,18 @@ public:
   }
 
   virtual ~nsXULAlerts() {}
 
   nsresult ShowAlertNotification(const nsAString& aImageUrl, const nsAString& aAlertTitle,
                                  const nsAString& aAlertText, bool aAlertTextClickable,
                                  const nsAString& aAlertCookie, nsIObserver* aAlertListener,
                                  const nsAString& aAlertName, const nsAString& aBidi,
-                                 const nsAString& aLang, bool aInPrivateBrowsing);
+                                 const nsAString& aLang, nsIPrincipal* aPrincipal,
+                                 bool aInPrivateBrowsing);
 
   nsresult CloseAlert(const nsAString& aAlertName);
 protected:
   nsInterfaceHashtable<nsStringHashKey, nsIDOMWindow> mNamedWindows;
 };
 
 /**
  * This class wraps observers for alerts and watches
--- a/toolkit/components/alerts/resources/content/alert.js
+++ b/toolkit/components/alerts/resources/content/alert.js
@@ -27,19 +27,29 @@ function prefillAlertInfo() {
   // arguments[2] --> the alert text
   // arguments[3] --> is the text clickable?
   // arguments[4] --> the alert cookie to be passed back to the listener
   // arguments[5] --> the alert origin reported by the look and feel
   // arguments[6] --> bidi
   // arguments[7] --> lang
   // arguments[8] --> replaced alert window (nsIDOMWindow)
   // arguments[9] --> an optional callback listener (nsIObserver)
+  // arguments[10] -> the localized alert source string
 
   switch (window.arguments.length) {
     default:
+    case 11: {
+      let label = document.getElementById('alertSourceLabel');
+      if (window.arguments[10]) {
+        label.hidden = false;
+        label.setAttribute('value', window.arguments[10]);
+      } else {
+        label.hidden = true;
+      }
+    }
     case 10:
       gAlertListener = window.arguments[9];
     case 9:
       gReplacedWindow = window.arguments[8];
     case 8:
       if (window.arguments[7]) {
         document.getElementById('alertTitleLabel').setAttribute('lang', window.arguments[7]);
         document.getElementById('alertTextLabel').setAttribute('lang', window.arguments[7]);
--- a/toolkit/components/alerts/resources/content/alert.xul
+++ b/toolkit/components/alerts/resources/content/alert.xul
@@ -27,16 +27,18 @@
     <box>
       <hbox id="alertImageBox" class="alertImageBox" align="center" pack="center">
         <image id="alertImage"/>
       </hbox>
 
       <vbox id="alertTextBox" class="alertTextBox">
         <label id="alertTitleLabel" class="alertTitle plain"/>
         <label id="alertTextLabel" class="alertText plain"/>
+        <spacer flex="1"/>
+        <label id="alertSourceLabel" class="alertSource plain"/>
       </vbox>
     </box>
 
     <vbox class="alertCloseBox">
       <toolbarbutton class="alertCloseButton close-icon"
                      tooltiptext="&closeAlert.tooltip;"
                      onclick="event.stopPropagation();"
                      oncommand="close();"/>
--- a/toolkit/components/alerts/test/mochitest.ini
+++ b/toolkit/components/alerts/test/mochitest.ini
@@ -3,8 +3,10 @@ skip-if = buildapp == 'b2g'
 
 # Synchronous tests like test_alerts.html must come before
 # asynchronous tests like test_alerts_noobserve.html!
 [test_alerts.html]
 skip-if = toolkit == 'android'
 [test_alerts_noobserve.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_multiple_alerts.html]
+[test_principal.html]
+skip-if = toolkit == 'android'
--- a/toolkit/components/alerts/test/test_alerts.html
+++ b/toolkit/components/alerts/test/test_alerts.html
@@ -17,26 +17,26 @@
 <br>If so, the test will finish once the notification disappears.
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 var observer = {
   alertShow: false,
   observe: function (aSubject, aTopic, aData) {
-    if (aTopic == "alertclickcallback") { 
+    is(aData, "foobarcookie", "Checking whether the alert cookie was passed correctly");
+    if (aTopic == "alertclickcallback") {
       todo(false, "Did someone click the notification while running mochitests? (Please don't.)");
     } else if (aTopic == "alertshow") {
       ok(!this.alertShow, "Alert should not be shown more than once");
       this.alertShow = true;
     } else {
       is(aTopic, "alertfinished", "Checking the topic for a finished notification");
       SimpleTest.finish();
     }
-    is(aData, "foobarcookie", "Checking whether the alert cookie was passed correctly");
   }
 };
 
 function runTest() {
   const Cc = SpecialPowers.Cc;
   const Ci = SpecialPowers.Ci;
 
   if (!("@mozilla.org/alerts-service;1" in Cc)) {
@@ -57,19 +57,20 @@ function runTest() {
     return;
   }
 
   try {
     var alertName = "fiorello";
     SimpleTest.waitForExplicitFinish();
     notifier.showAlertNotification(null, "Notification test",
                                    "Surprise! I'm here to test notifications!",
-                                   false, "foobarcookie", observer, alertname);
+                                   false, "foobarcookie", observer, alertName);
     ok(true, "showAlertNotification() succeeded.  Waiting for notification...");
-    if (MAC) {
+
+    if (SpecialPowers.Services.appinfo.OS == "Darwin") {
       // Notifications are native on OS X 10.8 and later, and when they are they
       // persist in the Notification Center. We need to close explicitly to avoid a hang.
       // This also works for XUL notifications when running this test on OS X < 10.8.
       notifier.closeAlert(alertName);
     }
   } catch (ex) {
     todo(false, "showAlertNotification() failed.", ex);
     SimpleTest.finish();
new file mode 100644
--- /dev/null
+++ b/toolkit/components/alerts/test/test_principal.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 1202933</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+<p id="display"></p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+const Services = SpecialPowers.Services;
+
+const notifier = Cc["@mozilla.org/alerts-service;1"]
+                   .getService(Ci.nsIAlertsService);
+
+function notify(alertName, principal) {
+  return new Promise((resolve, reject) => {
+    var source;
+    function observe(subject, topic, data) {
+      if (topic == "alertclickcallback") {
+        reject(new Error("Alerts should not be clicked during test"));
+      } else if (topic == "alertshow") {
+        var alertWindows = Services.wm.getEnumerator("alert:alert");
+        ok(alertWindows.hasMoreElements(), "Should show alert");
+        var alertWindow = alertWindows.getNext();
+        source = alertWindow.document.getElementById("alertSourceLabel").getAttribute("value");
+      } else {
+        is(topic, "alertfinished", "Should hide alert");
+        resolve(source);
+      }
+    }
+    notifier.showAlertNotification(null, "Notification test",
+                                   "Surprise! I'm here to test notifications!",
+                                   false, alertName, observe, alertName,
+                                   null, null, null, principal);
+    if (SpecialPowers.Services.appinfo.OS == "Darwin") {
+      notifier.closeAlert(alertName);
+    }
+  });
+}
+
+function* testNoPrincipal() {
+  var source = yield notify("noPrincipal", null);
+  ok(!source, "Should omit source without principal");
+}
+
+function* testSystemPrincipal() {
+  var principal = Services.scriptSecurityManager.getSystemPrincipal();
+  var source = yield notify("systemPrincipal", principal);
+  ok(!source, "Should omit source for system principal");
+}
+
+function* testExpandedPrincipal() {
+  var principal = Services.scriptSecurityManager.createExpandedPrincipal([], 0);
+  var source = yield notify("expandedPrincipal", principal);
+  ok(!source, "Should omit source for expanded principal");
+}
+
+function* testNullPrincipal() {
+  var principal = Services.scriptSecurityManager.createNullPrincipal({});
+  var source = yield notify("nullPrincipal", principal);
+  ok(!source, "Should omit source for null principal");
+}
+
+function* testNodePrincipal() {
+  var principal = SpecialPowers.wrap(document).nodePrincipal;
+  var source = yield notify("nodePrincipal", principal);
+
+  var stringBundle = Services.strings.createBundle(
+    "chrome://alerts/locale/alert.properties"
+  );
+  var localizedSource = stringBundle.formatStringFromName(
+    "source.label", [principal.URI.hostPort], 1);
+  is(source, localizedSource, "Should include source for node principal");
+}
+
+function runTest() {
+  if (!("@mozilla.org/alerts-service;1" in Cc)) {
+    todo(false, "Alerts service does not exist in this application");
+    return;
+  }
+
+  if ("@mozilla.org/system-alerts-service;1" in Cc) {
+    todo(false, "Native alerts service exists in this application");
+    return;
+  }
+
+  ok(true, "Alerts service exists in this application");
+
+  ok(!Services.wm.getEnumerator("alert:alert").hasMoreElements(),
+     "Alerts should not be present at the start of the test.");
+
+  add_task(testNoPrincipal);
+  add_task(testSystemPrincipal);
+  add_task(testExpandedPrincipal);
+  add_task(testNullPrincipal);
+  add_task(testNodePrincipal);
+}
+
+runTest();
+</script>
+</pre>
+</body>
+</html>
--- a/toolkit/locales/en-US/chrome/alerts/alert.properties
+++ b/toolkit/locales/en-US/chrome/alerts/alert.properties
@@ -4,8 +4,12 @@
 
 # LOCALIZATION NOTE(closeButton.title): Used as the close button text for web notifications on OS X.
 # This should ideally match the string that OS X uses for the close button on alert-type
 # notifications. OS X will truncate the value if it's too long.
 closeButton.title = Close
 # LOCALIZATION NOTE(actionButton.label): Used as the button label to provide more actions on OS X notifications. OS X will truncate this if it's too long.
 actionButton.label = …
 webActions.disable.label = Disable notifications from this site
+# LOCALIZATION NOTE(source.label): Used to show the URL of the site that
+# sent the notification (e.g., "via mozilla.org"). "%1$S" is the source host
+# and port.
+source.label=via %1$S
--- a/toolkit/themes/shared/alert-common.css
+++ b/toolkit/themes/shared/alert-common.css
@@ -33,16 +33,20 @@
   background-image: linear-gradient(rgba(255,255,255,0.4), rgba(255,255,255,0.3));
 }
 
 .alertTitle {
   font-weight: bold;
   font-size: 110%;
 }
 
+.alertSource {
+  color: gray;
+}
+
 #alertImage {
   max-width: 48px;
   max-height: 48px;
   list-style-image: url(chrome://global/skin/alerts/notification-48.png);
 }
 
 #alertNotification[clickable="true"] {
   cursor: pointer;