Bug 1516704 incognito support in session api, r=rpl,mikedeboer
authorShane Caraveo <scaraveo@mozilla.com>
Tue, 29 Jan 2019 20:18:36 +0000
changeset 455976 ac4d65b918938cf6e110bd03d8d993bd22f7a798
parent 455975 015cdc788e05a9dafc0abe737c9d16153f607b4c
child 455977 453fbc9760d4a46d3c7e33886160f71a790a4b25
push id35465
push usershindli@mozilla.com
push dateWed, 30 Jan 2019 04:10:12 +0000
treeherdermozilla-central@9e919be867b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrpl, mikedeboer
bugs1516704
milestone67.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 1516704 incognito support in session api, r=rpl,mikedeboer Differential Revision: https://phabricator.services.mozilla.com/D16921
browser/components/extensions/parent/ext-sessions.js
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_sessions_forgetClosedTab.js
browser/components/extensions/test/browser/browser_ext_sessions_forgetClosedWindow.js
browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js
browser/components/extensions/test/browser/browser_ext_sessions_incognito.js
browser/components/sessionstore/SessionStore.jsm
--- a/browser/components/extensions/parent/ext-sessions.js
+++ b/browser/components/extensions/parent/ext-sessions.js
@@ -13,25 +13,31 @@ ChromeUtils.defineModuleGetter(this, "Se
                                "resource:///modules/sessionstore/SessionStore.jsm");
 
 const SS_ON_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed";
 
 const getRecentlyClosed = (maxResults, extension) => {
   let recentlyClosed = [];
 
   // Get closed windows
+  // Closed private windows are not stored in sessionstore, we do
+  // not need to check access for that.
   let closedWindowData = SessionStore.getClosedWindowData(false);
   for (let window of closedWindowData) {
     recentlyClosed.push({
       lastModified: window.closedAt,
       window: Window.convertFromSessionStoreClosedData(extension, window)});
   }
 
   // Get closed tabs
+  // Private closed tabs are in sessionstore if the owning window is still open .
   for (let window of windowTracker.browserWindows()) {
+    if (!extension.canAccessWindow(window)) {
+      continue;
+    }
     let closedTabData = SessionStore.getClosedTabData(window, false);
     for (let tab of closedTabData) {
       recentlyClosed.push({
         lastModified: tab.closedAt,
         tab: Tab.convertFromSessionStoreClosedData(extension, tab, window)});
     }
   }
 
@@ -61,44 +67,46 @@ const getEncodedKey = function getEncode
       "Sessions API storage methods will not work with a temporary addon ID. " +
       "Please add an explicit addon ID to your manifest.";
     throw new ExtensionError(message);
   }
 
   return `extension:${extensionId}:${key}`;
 };
 
-const getTabParams = function getTabParams(extensionId, key, id) {
-  let encodedKey = getEncodedKey(extensionId, key);
-  let tab = tabTracker.getTab(id);
-
-  return {encodedKey, tab};
-};
-
-const getWindowParams = function getWindowParams(extensionId, key, id, context) {
-  let encodedKey = getEncodedKey(extensionId, key);
-  let win = windowTracker.getWindow(id, context);
-
-  return {encodedKey, win};
-};
-
 this.sessions = class extends ExtensionAPI {
   getAPI(context) {
     let {extension} = context;
+
+    function getTabParams(key, id) {
+      let encodedKey = getEncodedKey(extension.id, key);
+      let tab = tabTracker.getTab(id);
+      if (!context.canAccessWindow(tab.ownerGlobal)) {
+        throw new ExtensionError(`Invalid tab ID: ${id}`);
+      }
+      return {encodedKey, tab};
+    }
+
+    function getWindowParams(key, id) {
+      let encodedKey = getEncodedKey(extension.id, key);
+      let win = windowTracker.getWindow(id, context);
+      return {encodedKey, win};
+    }
+
     return {
       sessions: {
         async getRecentlyClosed(filter) {
           await SessionStore.promiseInitialized;
           let maxResults = filter.maxResults == undefined ? this.MAX_SESSION_RESULTS : filter.maxResults;
           return getRecentlyClosed(maxResults, extension);
         },
 
         async forgetClosedTab(windowId, sessionId) {
           await SessionStore.promiseInitialized;
-          let window = context.extension.windowManager.get(windowId).window;
+          let window = windowTracker.getWindow(windowId, context);
           let closedTabData = SessionStore.getClosedTabData(window, false);
 
           let closedTabIndex = closedTabData.findIndex((closedTab) => {
             return closedTab.closedId === parseInt(sessionId, 10);
           });
 
           if (closedTabIndex < 0) {
             throw new ExtensionError(`Could not find closed tab using sessionId ${sessionId}.`);
@@ -122,89 +130,91 @@ this.sessions = class extends ExtensionA
           SessionStore.forgetClosedWindow(closedWindowIndex);
         },
 
         async restore(sessionId) {
           await SessionStore.promiseInitialized;
           let session, closedId;
           if (sessionId) {
             closedId = sessionId;
-            session = SessionStore.undoCloseById(closedId);
+            session = SessionStore.undoCloseById(closedId, extension.privateBrowsingAllowed);
           } else if (SessionStore.lastClosedObjectType == "window") {
             // If the most recently closed object is a window, just undo closing the most recent window.
             session = SessionStore.undoCloseWindow(0);
           } else {
             // It is a tab, and we cannot call SessionStore.undoCloseTab without a window,
             // so we must find the tab in which case we can just use its closedId.
             let recentlyClosedTabs = [];
             for (let window of windowTracker.browserWindows()) {
               let closedTabData = SessionStore.getClosedTabData(window, false);
               for (let tab of closedTabData) {
                 recentlyClosedTabs.push(tab);
               }
             }
 
-            // Sort the tabs.
-            recentlyClosedTabs.sort((a, b) => b.closedAt - a.closedAt);
+            if (recentlyClosedTabs.length > 0) {
+              // Sort the tabs.
+              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);
+              // Use the closedId of the most recently closed tab to restore it.
+              closedId = recentlyClosedTabs[0].closedId;
+              session = SessionStore.undoCloseById(closedId, extension.privateBrowsingAllowed);
+            }
           }
           return createSession(session, extension, closedId);
         },
 
         setTabValue(tabId, key, value) {
           let {tab, encodedKey} =
-            getTabParams(extension.id, key, tabId);
+            getTabParams(key, tabId);
 
           SessionStore.setCustomTabValue(tab, encodedKey, JSON.stringify(value));
         },
 
         async getTabValue(tabId, key) {
           let {tab, encodedKey} =
-            getTabParams(extension.id, key, tabId);
+            getTabParams(key, tabId);
 
           let value = SessionStore.getCustomTabValue(tab, encodedKey);
           if (value) {
             return JSON.parse(value);
           }
 
           return undefined;
         },
 
         removeTabValue(tabId, key) {
           let {tab, encodedKey} =
-            getTabParams(extension.id, key, tabId);
+            getTabParams(key, tabId);
 
           SessionStore.deleteCustomTabValue(tab, encodedKey);
         },
 
         setWindowValue(windowId, key, value) {
           let {win, encodedKey} =
-            getWindowParams(extension.id, key, windowId, context);
+            getWindowParams(key, windowId);
 
           SessionStore.setCustomWindowValue(win, encodedKey, JSON.stringify(value));
         },
 
         async getWindowValue(windowId, key) {
           let {win, encodedKey} =
-            getWindowParams(extension.id, key, windowId, context);
+            getWindowParams(key, windowId);
 
           let value = SessionStore.getCustomWindowValue(win, encodedKey);
           if (value) {
             return JSON.parse(value);
           }
 
           return undefined;
         },
 
         removeWindowValue(windowId, key) {
           let {win, encodedKey} =
-            getWindowParams(extension.id, key, windowId, context);
+            getWindowParams(key, windowId);
 
           SessionStore.deleteCustomWindowValue(win, encodedKey);
         },
 
         onChanged: new EventManager({
           context,
           name: "sessions.onChanged",
           register: fire => {
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -156,16 +156,17 @@ disabled = bug 1438663
 skip-if = !e10s || !crashreporter # the tab's process is killed during the test. Without e10s the parent process would die too.
 [browser_ext_port_disconnect_on_window_close.js]
 [browser_ext_runtime_openOptionsPage.js]
 [browser_ext_runtime_openOptionsPage_uninstall.js]
 [browser_ext_search.js]
 [browser_ext_runtime_setUninstallURL.js]
 [browser_ext_sessions_forgetClosedTab.js]
 [browser_ext_sessions_forgetClosedWindow.js]
+[browser_ext_sessions_incognito.js]
 [browser_ext_sessions_getRecentlyClosed.js]
 [browser_ext_sessions_getRecentlyClosed_private.js]
 [browser_ext_sessions_getRecentlyClosed_tabs.js]
 [browser_ext_sessions_restore.js]
 [browser_ext_sessions_restoreTab.js]
 [browser_ext_sessions_window_tab_value.js]
 [browser_ext_settings_overrides_default_search.js]
 [browser_ext_sidebarAction.js]
--- a/browser/components/extensions/test/browser/browser_ext_sessions_forgetClosedTab.js
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_forgetClosedTab.js
@@ -1,41 +1,43 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-add_task(async function test_sessions_forget_closed_tab() {
+function getExtension(incognitoOverride) {
   function background() {
     browser.test.onMessage.addListener((msg, windowId, sessionId) => {
       if (msg === "check-sessions") {
         browser.sessions.getRecentlyClosed().then(recentlyClosed => {
           browser.test.sendMessage("recentlyClosed", recentlyClosed);
         });
       } else if (msg === "forget-tab") {
         browser.sessions.forgetClosedTab(windowId, sessionId).then(
           () => {
             browser.test.sendMessage("forgot-tab");
           },
           error => {
-            browser.test.assertEq(error.message,
-                                  `Could not find closed tab using sessionId ${sessionId}.`);
-            browser.test.sendMessage("forget-reject");
+            browser.test.sendMessage("forget-reject", error.message);
           }
         );
       }
     });
   }
 
-  let extension = ExtensionTestUtils.loadExtension({
+  return ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["sessions", "tabs"],
     },
     background,
+    incognitoOverride,
   });
+}
 
+add_task(async function test_sessions_forget_closed_tab() {
+  let extension = getExtension();
   await extension.startup();
 
   let tabUrl = "http://example.com";
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, tabUrl);
   BrowserTestUtils.removeTab(tab);
   tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, tabUrl);
   let sessionUpdatePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
   BrowserTestUtils.removeTab(tab);
@@ -57,18 +59,57 @@ add_task(async function test_sessions_fo
      "One tab was forgotten.");
   is(remainingClosed[0].tab.sessionId, recentlyClosed[1].tab.sessionId,
      "The correct tab was forgotten.");
 
   // Check that re-forgetting the same tab fails properly
   extension.sendMessage("forget-tab",
                         recentlyClosedTab.windowId,
                         recentlyClosedTab.sessionId);
-  await extension.awaitMessage("forget-reject");
+  let errormsg = await extension.awaitMessage("forget-reject");
+  is(errormsg, `Could not find closed tab using sessionId ${recentlyClosedTab.sessionId}.`);
+
   extension.sendMessage("check-sessions");
   remainingClosed = await extension.awaitMessage("recentlyClosed");
   is(remainingClosed.length, recentlyClosedLength - 1,
      "No extra tab was forgotten.");
   is(remainingClosed[0].tab.sessionId, recentlyClosed[1].tab.sessionId,
      "The correct tab remains.");
 
   await extension.unload();
 });
+
+add_task(async function test_sessions_forget_closed_tab_private() {
+  SpecialPowers.pushPrefEnv({set: [
+    ["extensions.allowPrivateBrowsingByDefault", false],
+  ]});
+
+  let pb_extension = getExtension("spanning");
+  await pb_extension.startup();
+  let extension = getExtension();
+  await extension.startup();
+
+  // Open a private browsing window.
+  let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
+
+  let tabUrl = "http://example.com";
+  let tab = await BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, tabUrl);
+  BrowserTestUtils.removeTab(tab);
+  tab = await BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, tabUrl);
+  let sessionUpdatePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
+  BrowserTestUtils.removeTab(tab);
+  await sessionUpdatePromise;
+
+  pb_extension.sendMessage("check-sessions");
+  let recentlyClosed = await pb_extension.awaitMessage("recentlyClosed");
+  let recentlyClosedTab = recentlyClosed[0].tab;
+
+  // Check that forgetting a tab works properly
+  extension.sendMessage("forget-tab",
+                        recentlyClosedTab.windowId,
+                        recentlyClosedTab.sessionId);
+  let errormsg = await extension.awaitMessage("forget-reject");
+  ok(/Invalid window ID/.test(errormsg), "could not access window");
+
+  await BrowserTestUtils.closeWindow(privateWin);
+  await extension.unload();
+  await pb_extension.unload();
+});
--- a/browser/components/extensions/test/browser/browser_ext_sessions_forgetClosedWindow.js
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_forgetClosedWindow.js
@@ -1,73 +1,105 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-add_task(async function test_sessions_forget_closed_window() {
-  async function openAndCloseWindow(url = "http://example.com") {
-    let win = await BrowserTestUtils.openNewBrowserWindow();
-    await BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, url);
-    await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
-    await BrowserTestUtils.closeWindow(win);
-  }
-
+function getExtension(incognitoOverride) {
   function background() {
     browser.test.onMessage.addListener((msg, sessionId) => {
       if (msg === "check-sessions") {
         browser.sessions.getRecentlyClosed().then(recentlyClosed => {
           browser.test.sendMessage("recentlyClosed", recentlyClosed);
         });
       } else if (msg === "forget-window") {
         browser.sessions.forgetClosedWindow(sessionId).then(
           () => {
             browser.test.sendMessage("forgot-window");
           },
           error => {
-            browser.test.assertEq(
-              error.message,
-              `Could not find closed window using sessionId ${sessionId}.`);
-            browser.test.sendMessage("forget-reject");
+            browser.test.sendMessage("forget-reject", error.message);
           }
         );
       }
     });
   }
 
-  let extension = ExtensionTestUtils.loadExtension({
+  return ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["sessions", "tabs"],
     },
     background,
+    incognitoOverride,
   });
+}
 
+async function openAndCloseWindow(url = "http://example.com", privateWin) {
+  let win = await BrowserTestUtils.openNewBrowserWindow({private: privateWin});
+  let tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
+  let sessionUpdatePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
+  await BrowserTestUtils.closeWindow(win);
+  await sessionUpdatePromise;
+}
+
+add_task(async function test_sessions_forget_closed_window() {
+  let extension = getExtension();
   await extension.startup();
 
   await openAndCloseWindow("about:config");
   await openAndCloseWindow("about:robots");
 
   extension.sendMessage("check-sessions");
   let recentlyClosed = await extension.awaitMessage("recentlyClosed");
-  let recentlyClosedLength = recentlyClosed.length;
   let recentlyClosedWindow = recentlyClosed[0].window;
 
   // Check that forgetting a window works properly
   extension.sendMessage("forget-window", recentlyClosedWindow.sessionId);
   await extension.awaitMessage("forgot-window");
   extension.sendMessage("check-sessions");
   let remainingClosed = await extension.awaitMessage("recentlyClosed");
-  is(remainingClosed.length, recentlyClosedLength - 1,
+  is(remainingClosed.length, recentlyClosed.length - 1,
      "One window was forgotten.");
   is(remainingClosed[0].window.sessionId, recentlyClosed[1].window.sessionId,
      "The correct window was forgotten.");
 
   // Check that re-forgetting the same window fails properly
   extension.sendMessage("forget-window", recentlyClosedWindow.sessionId);
-  await extension.awaitMessage("forget-reject");
+  let errMsg = await extension.awaitMessage("forget-reject");
+  is(errMsg, `Could not find closed window using sessionId ${recentlyClosedWindow.sessionId}.`);
+
   extension.sendMessage("check-sessions");
   remainingClosed = await extension.awaitMessage("recentlyClosed");
-  is(remainingClosed.length, recentlyClosedLength - 1,
+  is(remainingClosed.length, recentlyClosed.length - 1,
      "No extra window was forgotten.");
   is(remainingClosed[0].window.sessionId, recentlyClosed[1].window.sessionId,
      "The correct window remains.");
 
   await extension.unload();
 });
+
+add_task(async function test_sessions_forget_closed_window_private() {
+  SpecialPowers.pushPrefEnv({set: [
+    ["extensions.allowPrivateBrowsingByDefault", false],
+  ]});
+
+  let pb_extension = getExtension("spanning");
+  await pb_extension.startup();
+  let extension = getExtension("not_allowed");
+  await extension.startup();
+
+  await openAndCloseWindow("about:config", true);
+  await openAndCloseWindow("about:robots", true);
+
+  pb_extension.sendMessage("check-sessions");
+  let recentlyClosed = await pb_extension.awaitMessage("recentlyClosed");
+  let recentlyClosedWindow = recentlyClosed[0].window;
+
+  extension.sendMessage("forget-window", recentlyClosedWindow.sessionId);
+  await extension.awaitMessage("forgot-window");
+  extension.sendMessage("check-sessions");
+  let remainingClosed = await extension.awaitMessage("recentlyClosed");
+  is(remainingClosed.length, recentlyClosed.length - 1,
+     "One window was forgotten.");
+  ok(!recentlyClosedWindow.incognito, "not an incognito window");
+
+  await extension.unload();
+  await pb_extension.unload();
+});
--- a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js
@@ -2,32 +2,33 @@
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 SimpleTest.requestCompleteLog();
 
 Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href,
                                     this);
 
-add_task(async function test_sessions_get_recently_closed_private() {
+async function run_test_extension(incognitoOverride) {
   function background() {
     browser.test.onMessage.addListener((msg, filter) => {
       if (msg == "check-sessions") {
         browser.sessions.getRecentlyClosed(filter).then(recentlyClosed => {
           browser.test.sendMessage("recentlyClosed", recentlyClosed);
         });
       }
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["sessions", "tabs"],
     },
     background,
+    incognitoOverride,
   });
 
   // Open a private browsing window.
   let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
 
   await extension.startup();
 
   let {Management: {global: {windowTracker}}} = ChromeUtils.import("resource://gre/modules/Extension.jsm", null);
@@ -43,19 +44,37 @@ add_task(async function test_sessions_ge
 
   tab = await BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, "http://example.com");
   let sessionPromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
   BrowserTestUtils.removeTab(tab);
   await sessionPromise;
 
   extension.sendMessage("check-sessions");
   recentlyClosed = await extension.awaitMessage("recentlyClosed");
-  checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), 2, privateWinId, true);
+  let expectedCount = incognitoOverride == "not_allowed" ? 0 : 2;
+  checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), expectedCount, privateWinId, true);
 
   // Close the private window.
   await BrowserTestUtils.closeWindow(privateWin);
 
   extension.sendMessage("check-sessions");
   recentlyClosed = await extension.awaitMessage("recentlyClosed");
   is(recentlyClosed.filter(onlyNewItemsFilter).length, 0, "the closed private window info was not found in recently closed data");
 
   await extension.unload();
+}
+
+add_task(async function test_sessions_get_recently_closed_default() {
+  SpecialPowers.pushPrefEnv({set: [
+    ["extensions.allowPrivateBrowsingByDefault", true],
+  ]});
+
+  await run_test_extension();
 });
+
+add_task(async function test_sessions_get_recently_closed_private_incognito() {
+  SpecialPowers.pushPrefEnv({set: [
+    ["extensions.allowPrivateBrowsingByDefault", false],
+  ]});
+
+  await run_test_extension("spanning");
+  await run_test_extension("not_allowed");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_incognito.js
@@ -0,0 +1,88 @@
+"use strict";
+
+add_task(async function test_sessions_tab_value_private() {
+  Services.obs.notifyObservers(null, "browser:purge-session-history");
+  is(SessionStore.getClosedWindowCount(), 0,
+     "No closed window sessions at start of test");
+
+  SpecialPowers.pushPrefEnv({set: [
+    ["extensions.allowPrivateBrowsingByDefault", false],
+  ]});
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: "exampleextension@mozilla.org",
+        },
+      },
+      permissions: ["sessions"],
+    },
+    background() {
+      browser.test.onMessage.addListener(async (msg, pbw) => {
+        if (msg == "value") {
+          await browser.test.assertRejects(browser.sessions.setWindowValue(pbw.windowId, "foo", "bar"),
+                                           /Invalid window ID/,
+                                           "should not be able to set incognito window session data");
+          await browser.test.assertRejects(browser.sessions.getWindowValue(pbw.windowId, "foo"),
+                                           /Invalid window ID/,
+                                           "should not be able to get incognito window session data");
+          await browser.test.assertRejects(browser.sessions.removeWindowValue(pbw.windowId, "foo"),
+                                           /Invalid window ID/,
+                                           "should not be able to remove incognito window session data");
+          await browser.test.assertRejects(browser.sessions.setTabValue(pbw.tabId, "foo", "bar"),
+                                           /Invalid tab ID/,
+                                           "should not be able to set incognito tab session data");
+          await browser.test.assertRejects(browser.sessions.getTabValue(pbw.tabId, "foo"),
+                                           /Invalid tab ID/,
+                                           "should not be able to get incognito tab session data");
+          await browser.test.assertRejects(browser.sessions.removeTabValue(pbw.tabId, "foo"),
+                                           /Invalid tab ID/,
+                                           "should not be able to remove incognito tab session data");
+        }
+        if (msg == "restore") {
+          await browser.test.assertRejects(browser.sessions.restore(),
+                                           /Could not restore object/,
+                                           "should not be able to restore incognito last window session data");
+          if (pbw) {
+            await browser.test.assertRejects(browser.sessions.restore(pbw.sessionId),
+                                             /Could not restore object/,
+                                             `should not be able to restore incognito session ID ${pbw.sessionId} session data`);
+          }
+        }
+        browser.test.sendMessage("done");
+      });
+    },
+  });
+
+  let winData = await getIncognitoWindow("http://mochi.test:8888/");
+  await extension.startup();
+
+  // Test value set/get APIs on a private window and tab.
+  extension.sendMessage("value", winData.details);
+  await extension.awaitMessage("done");
+
+  // Test restoring a private tab.
+  let tab = await BrowserTestUtils.openNewForegroundTab(winData.win.gBrowser, "http://example.com");
+  let sessionUpdatePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
+  await BrowserTestUtils.removeTab(tab);
+  await sessionUpdatePromise;
+  let closedTabData = SessionStore.getClosedTabData(winData.win, false);
+
+  extension.sendMessage("restore", {sesionId: closedTabData[0].closedId});
+  await extension.awaitMessage("done");
+
+  // Test restoring a private window.
+  sessionUpdatePromise = BrowserTestUtils.waitForSessionStoreUpdate(winData.win.gBrowser.selectedTab);
+  await BrowserTestUtils.closeWindow(winData.win);
+  await sessionUpdatePromise;
+
+  is(SessionStore.getClosedWindowCount(), 0,
+     "The closed window was added to Recently Closed Windows");
+
+  // If the window gets restored, test will fail with an unclosed window.
+  extension.sendMessage("restore");
+  await extension.awaitMessage("done");
+
+  await extension.unload();
+});
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -370,18 +370,18 @@ var SessionStore = {
   navigateAndRestore(tab, loadArguments, historyIndex) {
     return SessionStoreInternal.navigateAndRestore(tab, loadArguments, historyIndex);
   },
 
   getSessionHistory(tab, updatedCallback) {
     return SessionStoreInternal.getSessionHistory(tab, updatedCallback);
   },
 
-  undoCloseById(aClosedId) {
-    return SessionStoreInternal.undoCloseById(aClosedId);
+  undoCloseById(aClosedId, aIncludePrivate) {
+    return SessionStoreInternal.undoCloseById(aClosedId, aIncludePrivate);
   },
 
   resetBrowserToLazyState(tab) {
     return SessionStoreInternal.resetBrowserToLazyState(tab);
   },
 
   /**
    * Determines whether the passed version number is compatible with
@@ -2684,17 +2684,16 @@ var SessionStoreInternal = {
   getClosedWindowData: function ssi_getClosedWindowData(aAsString = true) {
     return aAsString ? JSON.stringify(this._closedWindows) : Cu.cloneInto(this._closedWindows, {});
   },
 
   undoCloseWindow: function ssi_undoCloseWindow(aIndex) {
     if (!(aIndex in this._closedWindows)) {
       throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
     }
-
     // reopen the window
     let state = { windows: this._removeClosedWindow(aIndex) };
     delete state.windows[0].closedAt; // Window is now open.
 
     let window = this._openWindowWithState(state);
     this.windowToFocus = window;
     WINDOW_SHOWING_PROMISES.get(window).promise.then(win =>
       this.restoreWindows(win, state, {overwriteTabs: true}));
@@ -2823,29 +2822,34 @@ var SessionStoreInternal = {
 
 
   /**
    * Undoes the closing of a tab or window which corresponds
    * to the closedId passed in.
    *
    * @param aClosedId
    *        The closedId of the tab or window
+   * @param aIncludePrivate
+   *        Whether to restore private tabs or windows
    *
    * @returns a tab or window object
    */
-  undoCloseById(aClosedId) {
+  undoCloseById(aClosedId, aIncludePrivate = true) {
     // Check for a window first.
     for (let i = 0, l = this._closedWindows.length; i < l; i++) {
       if (this._closedWindows[i].closedId == aClosedId) {
         return this.undoCloseWindow(i);
       }
     }
 
     // Check for a tab.
     for (let window of Services.wm.getEnumerator("navigator:browser")) {
+      if (!aIncludePrivate && PrivateBrowsingUtils.isWindowPrivate(window)) {
+        continue;
+      }
       let windowState = this._windows[window.__SSi];
       if (windowState) {
         for (let j = 0, l = windowState._closedTabs.length; j < l; j++) {
           if (windowState._closedTabs[j].closedId == aClosedId) {
             return this.undoCloseTab(window, j);
           }
         }
       }