Bug 1320306 - Implement sessions.onChanged WebExtensions API, r?aswan draft
authorBob Silverberg <bsilverberg@mozilla.com>
Wed, 16 Nov 2016 14:30:20 -0500
changeset 445340 2940a0ca73d06367d4506098bb72225705ea915d
parent 444725 8387a4ada9a5c4cab059d8fafe0f8c933e83c149
child 538496 62608664c7430d52d6eca72b34ab6f9e54614c92
push id37488
push userbmo:bob.silverberg@gmail.com
push dateTue, 29 Nov 2016 13:58:56 +0000
reviewersaswan
bugs1320306
milestone53.0a1
Bug 1320306 - Implement sessions.onChanged WebExtensions API, r?aswan MozReview-Commit-ID: 14si74CKswB
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,23 @@
 /* -*- 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";
+
 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 +85,38 @@ 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 listenerCount = 0;
+
+        let observer = {
+          observe: function() {
+            this.emit("changed");
+          },
+        };
+        EventEmitter.decorate(observer);
+
+        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
@@ -1,19 +1,26 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 SimpleTest.requestCompleteLog();
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabStateFlusher",
+                                  "resource:///modules/sessionstore/TabStateFlusher.jsm");
 
 add_task(function* test_sessions_restore() {
   function background() {
+    let notificationCount = 0;
+    browser.sessions.onChanged.addListener(() => {
+      notificationCount++;
+      browser.test.sendMessage("notificationCount", notificationCount);
+    });
     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);
@@ -26,109 +33,129 @@ add_task(function* test_sessions_restore
           error => {
             browser.test.assertTrue(
               error.message.includes("Could not restore object using sessionId not-a-valid-session-id."));
             browser.test.sendMessage("restore-rejected");
           }
         );
       }
     });
+    browser.test.sendMessage("ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["sessions", "tabs"],
     },
     background,
   });
 
+  function* assertNotificationCount(expected) {
+    let notificationCount = yield extension.awaitMessage("notificationCount");
+    is(notificationCount, 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");
   }
 
+  yield extension.awaitMessage("ready");
+
   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);
+  yield assertNotificationCount(1);
 
   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");
+  yield assertNotificationCount(2);
   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);
+  yield assertNotificationCount(3);
 
   // Restore the window using the sessionId.
   extension.sendMessage("check-sessions");
   recentlyClosed = yield extension.awaitMessage("recentlyClosed");
   extension.sendMessage("restore", recentlyClosed[0].window.sessionId);
+  yield assertNotificationCount(4);
   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");
+  yield assertNotificationCount(5);
 
   // Open and close a tab.
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+  yield TabStateFlusher.flush(tab.linkedBrowser);
   yield BrowserTestUtils.removeTab(tab);
+  yield assertNotificationCount(6);
 
   // Restore the most recently closed item.
   extension.sendMessage("restore");
+  yield assertNotificationCount(7);
   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);
+  yield assertNotificationCount(8);
 
   // Restore the tab using the sessionId.
   extension.sendMessage("check-sessions");
   recentlyClosed = yield extension.awaitMessage("recentlyClosed");
   extension.sendMessage("restore", recentlyClosed[0].tab.sessionId);
+  yield assertNotificationCount(9);
   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);
+  yield assertNotificationCount(10);
 
   // Try to restore something with an invalid sessionId.
   extension.sendMessage("restore-reject");
   restored = yield extension.awaitMessage("restore-rejected");
 
   yield extension.unload();
 });