Bug 1308061 - Part 2: Implement sessions.onChanged WebExtensions API, r?aswan draft
authorBob Silverberg <bsilverberg@mozilla.com>
Wed, 16 Nov 2016 14:30:20 -0500
changeset 440451 0c79dc02caf357dea24e79e6403f0b7b74c66379
parent 439812 f6f70e6c37d800c80344d0566b967c1ea10b8cde
child 537369 36cb64e701c2f01b4bf1a77e31020819b569b717
push id36219
push userbmo:bob.silverberg@gmail.com
push dateThu, 17 Nov 2016 14:23:32 +0000
reviewersaswan
bugs1308061
milestone53.0a1
Bug 1308061 - Part 2: Implement sessions.onChanged WebExtensions API, r?aswan MozReview-Commit-ID: 7s6Wen1DcAL
browser/components/extensions/ext-sessions.js
browser/components/extensions/schemas/sessions.json
browser/components/extensions/test/browser/browser_ext_sessions_restore.js
--- a/browser/components/extensions/ext-sessions.js
+++ b/browser/components/extensions/ext-sessions.js
@@ -1,20 +1,32 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   promiseObserved,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
+const ssOnChangedTopic = "sessionstore-closed-objects-changed";
+
+let listenerCount = 0;
+
+let observer = {
+  observe: function(subject, topic, data) {
+    this.emit("changed", topic);
+  },
+};
+EventEmitter.decorate(observer);
+
 function getRecentlyClosed(maxResults, extension) {
   let recentlyClosed = [];
 
   // Get closed windows
   let closedWindowData = SessionStore.getClosedWindowData(false);
   for (let window of closedWindowData) {
     recentlyClosed.push({
       lastModified: window.closedAt,
@@ -82,11 +94,29 @@ extensions.registerSchemaAPI("sessions",
           recentlyClosedTabs.sort((a, b) => b.closedAt - a.closedAt);
 
           // Use the closedId of the most recently closed tab to restore it.
           closedId = recentlyClosedTabs[0].closedId;
           session = SessionStore.undoCloseById(closedId);
         }
         return createSession(session, extension, closedId);
       },
+      onChanged: new SingletonEventManager(context, "sessions.onChanged", fire => {
+        let listener = (event) => {
+          context.runSafe(fire);
+        };
+
+        observer.on("changed", listener);
+        listenerCount++;
+        if (listenerCount == 1) {
+          Services.obs.addObserver(observer, ssOnChangedTopic, false);
+        }
+        return () => {
+          observer.off("changed", listener);
+          listenerCount -= 1;
+          if (!listenerCount) {
+            Services.obs.removeObserver(observer, ssOnChangedTopic);
+          }
+        };
+      }).api(),
     },
   };
 });
--- a/browser/components/extensions/schemas/sessions.json
+++ b/browser/components/extensions/schemas/sessions.json
@@ -126,17 +126,16 @@
             ]
           }
         ]
       }
     ],
     "events": [
       {
         "name": "onChanged",
-        "unsupported": true,
         "description": "Fired when recently closed tabs and/or windows are changed. This event does not monitor synced sessions changes.",
         "type": "function"
       }
     ],
     "properties": {
       "MAX_SESSION_RESULTS": {
         "value": 25,
         "description": "The maximum number of $(ref:sessions.Session) that will be included in a requested list."
--- a/browser/components/extensions/test/browser/browser_ext_sessions_restore.js
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_restore.js
@@ -4,16 +4,24 @@
 
 SimpleTest.requestCompleteLog();
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
 add_task(function* test_sessions_restore() {
   function background() {
+    let notificationCount = 0;
+    browser.sessions.onChanged.addListener(function listener() {
+      notificationCount++;
+      browser.test.sendMessage("notificationCount", notificationCount);
+      if (notificationCount == 10) {
+        browser.sessions.onChanged.removeListener(listener);
+      }
+    });
     browser.test.onMessage.addListener((msg, data) => {
       if (msg == "check-sessions") {
         browser.sessions.getRecentlyClosed().then(recentlyClosed => {
           browser.test.sendMessage("recentlyClosed", recentlyClosed);
         });
       } else if (msg == "restore") {
         browser.sessions.restore(data).then(sessions => {
           browser.test.sendMessage("restored", sessions);
@@ -35,16 +43,20 @@ add_task(function* test_sessions_restore
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["sessions", "tabs"],
     },
     background,
   });
 
+  function assertNotificationCount(expected, actual) {
+    is(actual, expected, "the expected number of notifications was fired");
+  }
+
   yield extension.startup();
 
   let {Management: {global: {WindowManager, TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
   function checkLocalTab(tab, expectedUrl) {
     let realTab = TabManager.getTab(tab.id);
     let tabState = JSON.parse(SessionStore.getTabState(realTab));
     is(tabState.entries[0].url, expectedUrl, "restored tab has the expected url");
@@ -52,83 +64,103 @@ add_task(function* test_sessions_restore
 
   let win = yield BrowserTestUtils.openNewBrowserWindow();
   yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "about:config");
   yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
   for (let url of ["about:robots", "about:mozilla"]) {
     yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
   }
   yield BrowserTestUtils.closeWindow(win);
+  let notificationCount = yield extension.awaitMessage("notificationCount");
+  assertNotificationCount(1, notificationCount);
 
   extension.sendMessage("check-sessions");
   let recentlyClosed = yield extension.awaitMessage("recentlyClosed");
 
   // Check that our expected window is the most recently closed.
   is(recentlyClosed[0].window.tabs.length, 3, "most recently closed window has the expected number of tabs");
 
   // Restore the window.
   extension.sendMessage("restore");
+  notificationCount = yield extension.awaitMessage("notificationCount");
+  assertNotificationCount(2, notificationCount);
   let restored = yield extension.awaitMessage("restored");
 
   is(restored.length, 1, "restore returned the expected number of sessions");
   is(restored[0].window.tabs.length, 3, "restore returned a window with the expected number of tabs");
   checkLocalTab(restored[0].window.tabs[0], "about:config");
   checkLocalTab(restored[0].window.tabs[1], "about:robots");
   checkLocalTab(restored[0].window.tabs[2], "about:mozilla");
 
   // Close the window again.
   let window = WindowManager.getWindow(restored[0].window.id);
   yield BrowserTestUtils.closeWindow(window);
+  notificationCount = yield extension.awaitMessage("notificationCount");
+  assertNotificationCount(3, notificationCount);
 
   // Restore the window using the sessionId.
   extension.sendMessage("check-sessions");
   recentlyClosed = yield extension.awaitMessage("recentlyClosed");
   extension.sendMessage("restore", recentlyClosed[0].window.sessionId);
+  notificationCount = yield extension.awaitMessage("notificationCount");
+  assertNotificationCount(4, notificationCount);
   restored = yield extension.awaitMessage("restored");
 
   is(restored.length, 1, "restore returned the expected number of sessions");
   is(restored[0].window.tabs.length, 3, "restore returned a window with the expected number of tabs");
   checkLocalTab(restored[0].window.tabs[0], "about:config");
   checkLocalTab(restored[0].window.tabs[1], "about:robots");
   checkLocalTab(restored[0].window.tabs[2], "about:mozilla");
 
   // Close the window again.
   window = WindowManager.getWindow(restored[0].window.id);
   yield BrowserTestUtils.closeWindow(window);
+  notificationCount = yield extension.awaitMessage("notificationCount");
+  assertNotificationCount(5, notificationCount);
 
   // Open and close a tab.
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
   yield BrowserTestUtils.removeTab(tab);
+  notificationCount = yield extension.awaitMessage("notificationCount");
+  assertNotificationCount(6, notificationCount);
 
   // Restore the most recently closed item.
   extension.sendMessage("restore");
+  notificationCount = yield extension.awaitMessage("notificationCount");
+  assertNotificationCount(7, notificationCount);
   restored = yield extension.awaitMessage("restored");
 
   is(restored.length, 1, "restore returned the expected number of sessions");
   tab = restored[0].tab;
   ok(tab, "restore returned a tab");
   checkLocalTab(tab, "about:robots");
 
   // Close the tab again.
   let realTab = TabManager.getTab(tab.id);
   yield BrowserTestUtils.removeTab(realTab);
+  notificationCount = yield extension.awaitMessage("notificationCount");
+  assertNotificationCount(8, notificationCount);
 
   // Restore the tab using the sessionId.
   extension.sendMessage("check-sessions");
   recentlyClosed = yield extension.awaitMessage("recentlyClosed");
   extension.sendMessage("restore", recentlyClosed[0].tab.sessionId);
+  notificationCount = yield extension.awaitMessage("notificationCount");
+  assertNotificationCount(9, notificationCount);
   restored = yield extension.awaitMessage("restored");
 
   is(restored.length, 1, "restore returned the expected number of sessions");
   tab = restored[0].tab;
   ok(tab, "restore returned a tab");
   checkLocalTab(tab, "about:robots");
 
   // Close the tab again.
   realTab = TabManager.getTab(tab.id);
   yield BrowserTestUtils.removeTab(realTab);
+  notificationCount = yield extension.awaitMessage("notificationCount");
+  assertNotificationCount(10, notificationCount);
 
   // Try to restore something with an invalid sessionId.
   extension.sendMessage("restore-reject");
   restored = yield extension.awaitMessage("restore-rejected");
 
   yield extension.unload();
 });