Bug 774506 - Implement toast notification support. r=jaws
authorShane Caraveo
Sun, 26 Aug 2012 16:46:45 -0700
changeset 105567 224d02609af4cfd6a046914c0e8784605e0c6b9e
parent 105566 f79e4c7902a12ceef3367a7b03657f05859d3c60
child 105568 5dbb817a449c00c557a0691b0e7b3af2a611e38d
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersjaws
bugs774506
milestone17.0a1
Bug 774506 - Implement toast notification support. r=jaws
browser/base/content/test/browser_social_mozSocial_API.js
browser/base/content/test/social_sidebar.html
toolkit/components/social/MozSocialAPI.jsm
toolkit/components/social/WorkerAPI.jsm
toolkit/components/social/test/browser/Makefile.in
toolkit/components/social/test/browser/browser_notifications.js
--- a/browser/base/content/test/browser_social_mozSocial_API.js
+++ b/browser/base/content/test/browser_social_mozSocial_API.js
@@ -77,55 +77,10 @@ var tests = {
         // Let the other observers (like the one that updates the UI) run before
         // checking the icons.
         executeSoon(function () {
           iconsReady = true;
           checkNext();
         });
       }, "social:ambient-notification-changed", false);
     }
-  },
-
-  testServiceWindow: function(next) {
-    // our test provider was initialized in the test above, we just
-    // initiate our specific test now.
-    let port = Social.provider.port;
-    ok(port, "provider has a port");
-    port.postMessage({topic: "test-service-window"});
-    port.onmessage = function (e) {
-      let topic = e.data.topic;
-      switch (topic) {
-        case "got-service-window-message":
-          // The sidebar message will always come first, since it loads by default
-          ok(true, "got service window message");
-          // the service window URL should not be stored in history.
-          ensureSocialUrlNotRemembered(e.data.location);
-          port.postMessage({topic: "test-close-service-window"});
-          break;
-        case "got-service-window-closed-message":
-          ok(true, "got service window closed message");
-          next();
-          break;
-      }
-    }
-  },
-
-  testServiceWindowTwice: function(next) {
-    let port = Social.provider.port;
-    port.postMessage({topic: "test-service-window-twice"});
-    Social.provider.port.onmessage = function (e) {
-      let topic = e.data.topic;
-      switch (topic) {
-        case "test-service-window-twice-result":
-          is(e.data.result, "ok", "only one window should open when name is reused");
-          break;
-        case "got-service-window-message":
-          ok(true, "got service window message");
-          port.postMessage({topic: "test-close-service-window"});
-          break;
-        case "got-service-window-closed-message":
-          ok(true, "got service window closed message");
-          next();
-          break;
-      }
-    }
   }
 }
--- a/browser/base/content/test/social_sidebar.html
+++ b/browser/base/content/test/social_sidebar.html
@@ -1,48 +1,31 @@
 <html>
   <head>
     <meta charset="utf-8">
     <script>
-      var win;
+      var testwindow;
       function pingWorker() {
         var port = navigator.mozSocial.getWorker().port;
         port.onmessage = function(e) {
           var topic = e.data.topic;
           switch (topic) {
             case "test-flyout-open":
               navigator.mozSocial.openPanel("social_flyout.html");
               break;
             case "test-chatbox-open":
-              navigator.mozSocial.openChatWindow("social_chat.html", function(chatwin) {
-                port.postMessage({topic: "chatbox-opened", result: chatwin ? "ok" : "failed"});
-              });
-              break;
-            case "test-service-window":
-              win = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "width=300,height=300");
-              break;
-            case "test-service-window-twice":
-              win = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "width=300,height=300");
-              var win2 = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "");
-              var result;
-              if (win == win2)
-                result = "ok";
-              else
-                result = "not ok: " + win2 + " != " + win;
-              port.postMessage({topic: "test-service-window-twice-result", result: result});
-              break;
-            case "test-close-service-window":
-              win.addEventListener("unload", function watchClose() {
-                win.removeEventListener("unload", watchClose);
-                port.postMessage({topic: "service-window-closed-message", result: "ok"});
-              }, false)
-              win.close();
+              navigator.mozSocial.openChatWindow("social_chat.html",
+                function(chatwin) {
+                  port.postMessage({topic: "chatbox-opened",
+                                    result: chatwin ? "ok" : "failed"});
+                });
               break;
             case "test-isVisible":
-              port.postMessage({topic: "test-isVisible-response", result: navigator.mozSocial.isVisible});
+              port.postMessage({topic: "test-isVisible-response",
+                                result: navigator.mozSocial.isVisible});
               break;
           }
         }
         port.postMessage({topic: "sidebar-message", result: "ok"});
       }
     </script>
   </head>
   <body onload="pingWorker();">
--- a/toolkit/components/social/MozSocialAPI.jsm
+++ b/toolkit/components/social/MozSocialAPI.jsm
@@ -4,17 +4,17 @@
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SocialService", "resource://gre/modules/SocialService.jsm");
 
-const EXPORTED_SYMBOLS = ["MozSocialAPI"];
+const EXPORTED_SYMBOLS = ["MozSocialAPI", "openChatWindow"];
 
 var MozSocialAPI = {
   _enabled: false,
   _everEnabled: false,
   set enabled(val) {
     let enable = !!val;
     if (enable == this._enabled) {
       return;
@@ -96,25 +96,16 @@ function attachToWindow(provider, target
     hasBeenIdleFor: {
       enumerable: true,
       configurable: true,
       writable: true,
       value: function() {
         return false;
       }
     },
-    openServiceWindow: {
-      enumerable: true,
-      configurable: true,
-      writable: true,
-      value: function(toURL, name, options) {
-        let url = targetWindow.document.documentURIObject.resolve(toURL);
-        return openServiceWindow(provider, targetWindow, url, name, options);
-      }
-    },
     openChatWindow: {
       enumerable: true,
       configurable: true,
       writable: true,
       value: function(toURL, callback) {
         let url = targetWindow.document.documentURIObject.resolve(toURL);
         openChatWindow(getChromeWindow(targetWindow), provider, url, callback);
       }
@@ -190,66 +181,28 @@ function getChromeWindow(contentWin) {
 function ensureProviderOrigin(provider, url) {
   // resolve partial URLs and check prePath matches
   let uri;
   let fullURL;
   try {
     fullURL = Services.io.newURI(provider.origin, null, null).resolve(url);
     uri = Services.io.newURI(fullURL, null, null);
   } catch (ex) {
-    Cu.reportError("openServiceWindow: failed to resolve window URL: " + url + "; " + ex);
+    Cu.reportError("mozSocial: failed to resolve window URL: " + url + "; " + ex);
     return null;
   }
 
   if (provider.origin != uri.prePath) {
-    Cu.reportError("openServiceWindow: unable to load new location, " +
+    Cu.reportError("mozSocial: unable to load new location, " +
                    provider.origin + " != " + uri.prePath);
     return null;
   }
   return fullURL;
 }
 
 function openChatWindow(chromeWindow, provider, url, callback) {
   if (!chromeWindow.SocialChatBar)
     return;
   let fullURL = ensureProviderOrigin(provider, url);
   if (!fullURL)
     return;
   chromeWindow.SocialChatBar.newChat(provider, fullURL, callback);
 }
-
-function openServiceWindow(provider, contentWindow, url, name, options) {
-  // resolve partial URLs and check prePath matches
-  let fullURL = ensureProviderOrigin(provider, url);
-  if (!fullURL)
-    return null;
-
-  let windowName = provider.origin + name;
-  let chromeWindow = Services.ww.getWindowByName(windowName, null);
-  let tabbrowser = chromeWindow && chromeWindow.gBrowser;
-  if (tabbrowser &&
-      tabbrowser.selectedBrowser.getAttribute("origin") == provider.origin) {
-    return tabbrowser.contentWindow;
-  }
-
-  let serviceWindow = contentWindow.openDialog(fullURL, windowName,
-                                               "chrome=no,dialog=no" + options);
-
-  // Get the newly opened window's containing XUL window
-  chromeWindow = getChromeWindow(serviceWindow);
-
-  // set the window's name and origin attribute on its browser, so that it can
-  // be found via getWindowByName
-  chromeWindow.name = windowName;
-  chromeWindow.gBrowser.selectedBrowser.setAttribute("origin", provider.origin);
-
-  // disable global history for the new window.
-  chromeWindow.gBrowser.docShell.QueryInterface(Components.interfaces.nsIDocShellHistory).useGlobalHistory = false;
-
-  // we dont want the default title the browser produces, we'll fixup whenever
-  // it changes.
-  serviceWindow.addEventListener("DOMTitleChanged", function() {
-    let sep = xulWindow.document.documentElement.getAttribute("titlemenuseparator");
-    xulWindow.document.title = provider.name + sep + serviceWindow.document.title;
-  });
-
-  return serviceWindow;
-}
--- a/toolkit/components/social/WorkerAPI.jsm
+++ b/toolkit/components/social/WorkerAPI.jsm
@@ -65,39 +65,57 @@ WorkerAPI.prototype = {
       cookies.forEach(function(aCookie) {
         let [name, value] = aCookie.split("=");
         results.push({name: unescape(name.trim()),
                       value: unescape(value.trim())});
       });
       this._port.postMessage({topic: "social.cookies-get-response",
                               data: results});
     },
-    
-    // XXX backwards compat for existing providers, remove these eventually
-    "social.ambient-notification-area": function (data) {
-      // replaced with social.user-profile
-      // handle the provider icon and user profile for the primary provider menu
-      if (data.background) {
-        // backwards compat
-        try {
-          data.iconURL = /url\((['"]?)(.*)(\1)\)/.exec(data.background)[2];
-        } catch(e) {
-          data.iconURL = data.background;
+    'social.notification-create': function(data) {
+      let port = this._port;
+      let provider = this._provider;
+      let {id, type, icon, body, action, actionArgs} = data;
+      let alertsService = Cc["@mozilla.org/alerts-service;1"]
+                              .getService(Ci.nsIAlertsService);
+      function listener(subject, topic, data) {
+        if (topic === "alertclickcallback") {
+          // we always post back the click
+          port.postMessage({topic: "social.notification-action",
+                            data: {id: id,
+                                   action: action,
+                                   actionArgs: actionArgs}});
+          switch (action) {
+            case "link":
+              // if there is a url, make it open a tab
+              if (actionArgs.toURL) {
+                try {
+                  let pUri = Services.io.newURI(provider.origin, null, null);
+                  let nUri = Services.io.newURI(pUri.resolve(actionArgs.toURL),
+                                                null, null);
+                  // fixup
+                  if (nUri.scheme != pUri.scheme)
+                    nUri.scheme = pUri.scheme;
+                  if (nUri.prePath == provider.origin) {
+                    let xulWindow = Services.wm.getMostRecentWindow("navigator:browser");
+                    xulWindow.openUILink(nUri.spec);
+                  }
+                } catch(e) {
+                  Cu.reportError("social.notification-create error: "+e);
+                }
+              }
+              break;
+            default:
+              break;
+          }
         }
       }
-
-      this._provider.updateUserProfile(data);
+      alertsService.showAlertNotification(icon,
+                                          this._provider.name, // title
+                                          body,
+                                          !!action, // text clickable if an
+                                                    // action was provided.
+                                          null,
+                                          listener,
+                                          type); 
     },
-    "social.ambient-notification-update": function (data) {
-      // replaced with social.ambient-notification
-      // handle the provider icon and user profile for the primary provider menu
-      if (data.background) {
-        // backwards compat
-        try {
-          data.iconURL = /url\((['"]?)(.*)(\1)\)/.exec(data.background)[2];
-        } catch(e) {
-          data.iconURL = data.background;
-        }
-      }
-      this._provider.setAmbientNotification(data);
-    }
   }
 }
--- a/toolkit/components/social/test/browser/Makefile.in
+++ b/toolkit/components/social/test/browser/Makefile.in
@@ -17,11 +17,12 @@ MOCHITEST_BROWSER_FILES = \
   data.json \
   worker_xhr.js \
   browser_frameworker.js \
   worker_relative.js \
   relative_import.js \
   browser_workerAPI.js \
   worker_social.js \
   browser_SocialProvider.js \
+  browser_notifications.js \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/components/social/test/browser/browser_notifications.js
@@ -0,0 +1,128 @@
+/* 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/. */
+
+const TEST_PROVIDER_ORIGIN = 'http://example.com';
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function ensureProvider(workerFunction, cb) {
+  let manifest = {
+    origin: TEST_PROVIDER_ORIGIN,
+    name: "Example Provider",
+    workerURL: "data:application/javascript," + encodeURI("let run=" + workerFunction.toSource()) + ";run();"
+  };
+
+  ensureSocialEnabled();
+  SocialService.addProvider(manifest, function (p) {
+    cb(p);
+  });
+}
+
+function test() {
+  waitForExplicitFinish();
+
+  let cbPostTest = function(cb) {
+    SocialService.removeProvider(TEST_PROVIDER_ORIGIN, function() {cb()});
+  };
+  runTests(tests, undefined, cbPostTest);
+}
+
+let tests = {
+  testNotificationCallback: function(cbnext) {
+    let run = function() {
+      let testPort, apiPort;
+      onconnect = function(e) {
+        let port = e.ports[0];
+        port.onmessage = function(e) {
+          if (e.data.topic == "social.initialize") { // this is the api port.
+            apiPort = port;
+          } else if (e.data.topic == "test.initialize") { // test suite port.
+            testPort = port;
+            apiPort.postMessage({topic: 'social.notification-create',
+                                 data: {
+                                        id: "the id",
+                                        body: 'test notification',
+                                        action: 'callback',
+                                        actionArgs: { data: "something" }
+                                       }
+                                });
+          } else if (e.data.topic == "social.notification-action") {
+            let data = e.data.data;
+            let ok = data && data.action == "callback" &&
+                     data.actionArgs && e.data.data.actionArgs.data == "something";
+            testPort.postMessage({topic: "test.done", data: ok});
+          }
+        }
+      }
+    }
+    ensureProvider(run, function(provider) {
+      if ('@mozilla.org/system-alerts-service;1' in Cc) {
+        // This is a platform that has a system-alerts-service so the "toast"
+        // notifications aren't implemented by XUL making it very tricky to test.
+        // So just punt.
+        info("this platform has a system alerts service - test skipped");
+        cbnext();
+        return;
+      }
+      if (!("@mozilla.org/alerts-service;1" in Cc)) {
+        info("Alerts service does not exist in this application");
+        cbnext();
+        return;
+      }
+      var notifier;
+      try {
+        notifier = Cc["@mozilla.org/alerts-service;1"].
+                   getService(Ci.nsIAlertsService);
+      } catch (ex) {
+        info("Alerts service is not available. (Mac OS X without Growl?)", ex);
+        cbnext();
+        return;
+      }
+
+      provider.port.onmessage = function(e) {
+        if (e.data.topic == "test.done") {
+          ok(e.data.data, "check the test worked");
+          cbnext();
+        }
+      }
+      provider.port.postMessage({topic: "test.initialize"});
+      let count = 0;
+      // this relies on the implementation of the alerts service.
+      const ALERT_CHROME_URL = "chrome://global/content/alerts/alert.xul";
+      const ALERT_TEXT_LABEL_ID = "alertTextLabel";
+      const ALERT_TITLE_LABEL_ID = "alertTitleLabel";
+      let findPopup = function() {
+        let wenum = Services.ww.getWindowEnumerator();
+        while (wenum.hasMoreElements()) {
+          let win = wenum.getNext();
+          if (win.location.href == ALERT_CHROME_URL) {
+            let doc = win.document;
+            // We found the window - wait until the ID we care about also exists.
+            if (doc.getElementById(ALERT_TEXT_LABEL_ID) &&
+                doc.getElementById(ALERT_TEXT_LABEL_ID).getAttribute("value")) {
+              // some sanity checking of the content.
+              is(doc.getElementById(ALERT_TEXT_LABEL_ID).getAttribute("value"),
+                 "test notification",
+                 "check the alert label is correct");
+              is(doc.getElementById(ALERT_TITLE_LABEL_ID).getAttribute("value"),
+                 "Example Provider",
+                 "check the alert title is correct");
+              // now click on the text - this should trigger a "callback" into
+              // our worker which will then send back a "test.done" message.
+              EventUtils.sendMouseEvent({type: "click"}, ALERT_TEXT_LABEL_ID, win);
+              return;
+            }
+          }
+        }
+        if (count++ > 50) {
+          ok(false, "failed to find the notification popup");
+          cbnext();
+          return;
+        }
+        executeSoon(findPopup);
+      }
+      executeSoon(findPopup);
+    });
+  }
+};