Bug 1320306 - Implement sessions.onChanged WebExtensions API, r=aswan
authorBob Silverberg <bsilverberg@mozilla.com>
Wed, 16 Nov 2016 14:30:20 -0500
changeset 324765 3a44959def71
parent 324764 3118e664715b
child 324766 a2e634f475d3
push id31016
push userkwierso@gmail.com
push date2016-11-30 20:53 +0000
treeherdermozilla-central@8f1e42069983 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1320306
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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();
 });