Merge fx-team to central, a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Fri, 08 Jul 2016 15:02:31 -0700
changeset 304261 bbb29a9b88dd680dbb59577cbe4dc6e58d117100
parent 304260 fd8ff97bc294ec2d0f57408dae0c3b1e50ff62fa (current diff)
parent 304257 a30b1295e5968d8d45397eb94c42a00925b14c5c (diff)
child 304262 14b16ac3899119428c9c434d4e4e2c75f7a1e5f7
child 304368 7a7f440d578febfd991a3f657ae8f57815d27909
child 304389 7cd43da4f5bcecf51de8b8555e43f4691350df03
push id79280
push userkwierso@gmail.com
push dateFri, 08 Jul 2016 22:04:28 +0000
treeherdermozilla-inbound@14b16ac38991 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
first release with
nightly linux32
bbb29a9b88dd / 50.0a1 / 20160709030233 / files
nightly linux64
bbb29a9b88dd / 50.0a1 / 20160709030233 / files
nightly mac
bbb29a9b88dd / 50.0a1 / 20160709030201 / files
nightly win32
bbb29a9b88dd / 50.0a1 / 20160709030233 / files
nightly win64
bbb29a9b88dd / 50.0a1 / 20160709030233 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to central, a=merge CLOSED TREE
browser/themes/linux/tabbrowser/connecting.png
browser/themes/linux/tabbrowser/connecting@2x.png
browser/themes/osx/tabbrowser/connecting.png
browser/themes/osx/tabbrowser/connecting@2x.png
browser/themes/windows/tabbrowser/connecting.png
browser/themes/windows/tabbrowser/connecting@2x.png
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -449,26 +449,26 @@ MenuItem.prototype = {
     return true;
   },
 };
 
 var gExtensionCount = 0;
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("startup", (type, extension) => {
   gContextMenuMap.set(extension, new Map());
-  gRootItems.delete(extension);
   if (++gExtensionCount == 1) {
     Services.obs.addObserver(contextMenuObserver,
                              "on-build-contextmenu",
                              false);
   }
 });
 
 extensions.on("shutdown", (type, extension) => {
   gContextMenuMap.delete(extension);
+  gRootItems.delete(extension);
   if (--gExtensionCount == 0) {
     Services.obs.removeObserver(contextMenuObserver,
                                 "on-build-contextmenu");
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 extensions.registerSchemaAPI("contextMenus", (extension, context) => {
--- a/browser/components/extensions/test/browser/.eslintrc
+++ b/browser/components/extensions/test/browser/.eslintrc
@@ -14,13 +14,17 @@
     "PanelUI": false,
 
     // Test harness globals
     "ExtensionTestUtils": false,
     "TestUtils": false,
 
     "clickBrowserAction": true,
     "clickPageAction": true,
-    "CustomizableUI": true,
+    "closeContextMenu": true,
+    "closeExtensionContextMenu": true,
     "focusWindow": true,
     "makeWidgetId": true,
+    "openContextMenu": true,
+    "openExtensionContextMenu": true,
+    "CustomizableUI": true,
   }
 }
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -23,16 +23,20 @@ support-files =
 [browser_ext_browserAction_popup.js]
 [browser_ext_browserAction_popup_resize.js]
 [browser_ext_browserAction_simple.js]
 [browser_ext_commands_execute_page_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_contextMenus.js]
+[browser_ext_contextMenus_checkboxes.js]
+[browser_ext_contextMenus_icons.js]
+[browser_ext_contextMenus_radioGroups.js]
+[browser_ext_contextMenus_uninstall.js]
 [browser_ext_currentWindow.js]
 [browser_ext_getViews.js]
 [browser_ext_history.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
 [browser_ext_pageAction_popup.js]
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
@@ -22,46 +22,25 @@ add_task(function* () {
       });
       browser.test.notifyPass();
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish();
 
-  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
-  yield BrowserTestUtils.synthesizeMouseAtCenter("#img1", {
-    type: "contextmenu",
-    button: 2,
-  }, gBrowser.selectedBrowser);
-  yield popupShownPromise;
-
+  let contentAreaContextMenu = yield openContextMenu("#img1");
   let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
   is(item.length, 1, "contextMenu item for image was found");
-
-  let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
-  EventUtils.synthesizeMouseAtCenter(item[0], {});
-  yield popupHiddenPromise;
+  yield closeContextMenu();
 
-  contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
-  yield BrowserTestUtils.synthesizeMouseAtCenter("body", {
-    type: "contextmenu",
-    button: 2,
-  }, gBrowser.selectedBrowser);
-  yield popupShownPromise;
-
+  contentAreaContextMenu = yield openContextMenu("body");
   item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
   is(item.length, 0, "no contextMenu item for image was found");
-
-  // click something to close the context menu
-  popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
-  EventUtils.synthesizeMouseAtCenter(document.getElementById("context-selectall"), {});
-  yield popupHiddenPromise;
+  yield closeContextMenu();
 
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab1);
 });
 
 /* globals content */
 /* eslint-disable mozilla/no-cpows-in-tests */
@@ -137,260 +116,128 @@ add_task(function* () {
       browser.contextMenus.create({
         title: "child2",
         parentId: parentToDel,
         onclick: genericOnClick,
       });
       browser.contextMenus.remove(parentToDel);
 
       browser.contextMenus.create({
-        title: "radio-group-1",
-        type: "radio",
-        checked: true,
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
-        title: "Checkbox",
-        type: "checkbox",
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
-        title: "radio-group-2",
-        type: "radio",
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
-        title: "radio-group-2",
-        type: "radio",
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
-        type: "separator",
-      });
-
-      browser.contextMenus.create({
-        title: "Checkbox",
-        type: "checkbox",
-        checked: true,
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
-        title: "Checkbox",
-        type: "checkbox",
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
         title: "Without onclick property",
         id: "ext-without-onclick",
       });
 
       browser.contextMenus.update(parent, {parentId: child2}).then(
         () => {
-          browser.test.notifyFail();
+          browser.test.notifyFail("contextmenus");
         },
         () => {
-          browser.test.notifyPass();
+          browser.test.notifyPass("contextmenus");
         }
       );
     },
   });
 
   yield extension.startup();
-  yield extension.awaitFinish();
-
-  let contentAreaContextMenu;
-
-  function getTop() {
-    contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-    let items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
-    is(items.length, 1, "top level item was found (context=selection)");
-    let topItem = items[0];
-    return topItem.childNodes[0];
-  }
+  yield extension.awaitFinish("contextmenus");
 
-  function* openExtensionMenu() {
-    contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-    let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
-    yield BrowserTestUtils.synthesizeMouseAtCenter("#img1", {
-      type: "contextmenu",
-      button: 2,
-    }, gBrowser.selectedBrowser);
-    yield popupShownPromise;
+  let expectedClickInfo = {
+    menuItemId: "ext-image",
+    mediaType: "image",
+    srcUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/ctxmenu-image.png",
+    pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
+  };
 
-    popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
-    EventUtils.synthesizeMouseAtCenter(getTop(), {});
-    yield popupShownPromise;
+  function checkClickInfo(result) {
+    for (let i of Object.keys(expectedClickInfo)) {
+      is(result.info[i], expectedClickInfo[i],
+         "click info " + i + " expected to be: " + expectedClickInfo[i] + " but was: " + info[i]);
+    }
+    is(expectedClickInfo.pageSrc, result.tab.url);
   }
 
-  function* closeContextMenu(itemToSelect, expectedClickInfo, hasOnclickProperty = true) {
-    function checkClickInfo(info, tab) {
-      for (let i of Object.keys(expectedClickInfo)) {
-        is(info[i], expectedClickInfo[i],
-           "click info " + i + " expected to be: " + expectedClickInfo[i] + " but was: " + info[i]);
-      }
-      is(expectedClickInfo.pageSrc, tab.url);
-    }
-    let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
-    EventUtils.synthesizeMouseAtCenter(itemToSelect, {});
-
-    if (hasOnclickProperty) {
-      let {info, tab} = yield extension.awaitMessage("onclick");
-      if (expectedClickInfo) {
-        checkClickInfo(info, tab);
-      }
-    }
-
-    let {info, tab} = yield extension.awaitMessage("browser.contextMenus.onClicked");
-    if (expectedClickInfo) {
-      checkClickInfo(info, tab);
-    }
-
-    yield popupHiddenPromise;
-  }
-
-  function confirmRadioGroupStates(expectedStates) {
-    let top = getTop();
-
-    let radioItems = top.getElementsByAttribute("type", "radio");
-    let radioGroup1 = top.getElementsByAttribute("label", "radio-group-1");
-    let radioGroup2 = top.getElementsByAttribute("label", "radio-group-2");
-
-    is(radioItems.length, 3, "there should be 3 radio items in the context menu");
-    is(radioGroup1.length, 1, "the first radio group should only have 1 radio item");
-    is(radioGroup2.length, 2, "the second radio group should only have 2 radio items");
-
-    is(radioGroup1[0].hasAttribute("checked"), expectedStates[0], `radio item 1 has state (checked=${expectedStates[0]})`);
-    is(radioGroup2[0].hasAttribute("checked"), expectedStates[1], `radio item 2 has state (checked=${expectedStates[1]})`);
-    is(radioGroup2[1].hasAttribute("checked"), expectedStates[2], `radio item 3 has state (checked=${expectedStates[2]})`);
-  }
-
-  function confirmCheckboxStates(expectedStates) {
-    let checkboxItems = getTop().getElementsByAttribute("type", "checkbox");
-
-    is(checkboxItems.length, 3, "there should be 3 checkbox items in the context menu");
-
-    is(checkboxItems[0].hasAttribute("checked"), expectedStates[0], `checkbox item 1 has state (checked=${expectedStates[0]})`);
-    is(checkboxItems[1].hasAttribute("checked"), expectedStates[1], `checkbox item 2 has state (checked=${expectedStates[1]})`);
-    is(checkboxItems[2].hasAttribute("checked"), expectedStates[2], `checkbox item 3 has state (checked=${expectedStates[2]})`);
-  }
-
-  yield openExtensionMenu();
+  let extensionMenuRoot = yield openExtensionContextMenu();
 
   // Check some menu items
-  let top = getTop();
-  let items = top.getElementsByAttribute("label", "image");
+  let items = extensionMenuRoot.getElementsByAttribute("label", "image");
   is(items.length, 1, "contextMenu item for image was found (context=image)");
   let image = items[0];
 
-  items = top.getElementsByAttribute("label", "selection-edited");
+  items = extensionMenuRoot.getElementsByAttribute("label", "selection-edited");
   is(items.length, 0, "contextMenu item for selection was not found (context=image)");
 
-  items = top.getElementsByAttribute("label", "parentToDel");
+  items = extensionMenuRoot.getElementsByAttribute("label", "parentToDel");
   is(items.length, 0, "contextMenu item for removed parent was not found (context=image)");
 
-  items = top.getElementsByAttribute("label", "parent");
+  items = extensionMenuRoot.getElementsByAttribute("label", "parent");
   is(items.length, 1, "contextMenu item for parent was found (context=image)");
 
   is(items[0].childNodes[0].childNodes.length, 2, "child items for parent were found (context=image)");
 
   // Click on ext-image item and check the click results
-  yield closeContextMenu(image, {
-    menuItemId: "ext-image",
-    mediaType: "image",
-    srcUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/ctxmenu-image.png",
-    pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
-  });
-
-  // Test radio groups
-  yield openExtensionMenu();
-  confirmRadioGroupStates([true, false, false]);
-  items = getTop().getElementsByAttribute("type", "radio");
-  yield closeContextMenu(items[1]);
-
-  yield openExtensionMenu();
-  confirmRadioGroupStates([true, true, false]);
-  items = getTop().getElementsByAttribute("type", "radio");
-  yield closeContextMenu(items[2]);
-
-  yield openExtensionMenu();
-  confirmRadioGroupStates([true, false, true]);
-  items = getTop().getElementsByAttribute("type", "radio");
-  yield closeContextMenu(items[0]);
+  yield closeExtensionContextMenu(image);
 
-  yield openExtensionMenu();
-  confirmRadioGroupStates([true, false, true]);
-
-  // Test checkboxes
-  items = getTop().getElementsByAttribute("type", "checkbox");
-  confirmCheckboxStates([false, true, false]);
-  yield closeContextMenu(items[0]);
-
-  yield openExtensionMenu();
-  confirmCheckboxStates([true, true, false]);
-  items = getTop().getElementsByAttribute("type", "checkbox");
-  yield closeContextMenu(items[2]);
-
-  yield openExtensionMenu();
-  confirmCheckboxStates([true, true, true]);
-  items = getTop().getElementsByAttribute("type", "checkbox");
-  yield closeContextMenu(items[0]);
-
-  yield openExtensionMenu();
-  confirmCheckboxStates([false, true, true]);
-  items = getTop().getElementsByAttribute("type", "checkbox");
-  yield closeContextMenu(items[2]);
+  let result = yield extension.awaitMessage("onclick");
+  checkClickInfo(result);
+  result = yield extension.awaitMessage("browser.contextMenus.onClicked");
+  checkClickInfo(result);
 
   // Select some text
   yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
     let doc = content.document;
     let range = doc.createRange();
     let selection = content.getSelection();
     selection.removeAllRanges();
     let textNode = doc.getElementById("img1").previousSibling;
     range.setStart(textNode, 0);
     range.setEnd(textNode, 100);
     selection.addRange(range);
   });
 
   // Bring up context menu again
-  yield openExtensionMenu();
+  extensionMenuRoot = yield openExtensionContextMenu();
 
   // Check some menu items
-  top = getTop();
-  items = top.getElementsByAttribute("label", "Without onclick property");
+  items = extensionMenuRoot.getElementsByAttribute("label", "Without onclick property");
   is(items.length, 1, "contextMenu item was found (context=page)");
 
-  yield closeContextMenu(items[0], {
+  yield closeExtensionContextMenu(items[0]);
+
+  expectedClickInfo = {
     menuItemId: "ext-without-onclick",
     pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
-  }, false /* hasOnclickProperty */);
+  };
+
+  result = yield extension.awaitMessage("browser.contextMenus.onClicked");
+  checkClickInfo(result);
 
   // Bring up context menu again
-  yield openExtensionMenu();
+  extensionMenuRoot = yield openExtensionContextMenu();
 
   // Check some menu items
-  top = getTop();
-  items = top.getElementsByAttribute("label", "selection is: 'just some text 123456789012345678901234567890...'");
+  items = extensionMenuRoot.getElementsByAttribute("label", "selection is: 'just some text 123456789012345678901234567890...'");
   is(items.length, 1, "contextMenu item for selection was found (context=selection)");
   let selectionItem = items[0];
 
-  items = top.getElementsByAttribute("label", "selection");
+  items = extensionMenuRoot.getElementsByAttribute("label", "selection");
   is(items.length, 0, "contextMenu item label update worked (context=selection)");
 
-  yield closeContextMenu(selectionItem, {
+  yield closeExtensionContextMenu(selectionItem);
+
+  expectedClickInfo = {
     menuItemId: "ext-selection",
     pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
     selectionText: "just some text 1234567890123456789012345678901234567890123456789012345678901234567890123456789012",
-  });
+  };
 
+  result = yield extension.awaitMessage("onclick");
+  checkClickInfo(result);
+  result = yield extension.awaitMessage("browser.contextMenus.onClicked");
+  checkClickInfo(result);
+
+  let contentAreaContextMenu = yield openContextMenu("#img1");
   items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
   is(items.length, 0, "top level item was not found (after removeAll()");
+  yield closeContextMenu();
 
   yield extension.unload();
-
   yield BrowserTestUtils.removeTab(tab1);
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_checkboxes.js
@@ -0,0 +1,74 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+  gBrowser.selectedTab = tab1;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+    },
+
+    background: function() {
+      browser.contextMenus.create({
+        title: "Checkbox",
+        type: "checkbox",
+      });
+
+      browser.contextMenus.create({
+        type: "separator",
+      });
+
+      browser.contextMenus.create({
+        title: "Checkbox",
+        type: "checkbox",
+        checked: true,
+      });
+
+      browser.contextMenus.create({
+        title: "Checkbox",
+        type: "checkbox",
+      });
+
+      browser.test.notifyPass("contextmenus-checkboxes");
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("contextmenus-checkboxes");
+
+  function confirmCheckboxStates(extensionMenuRoot, expectedStates) {
+    let checkboxItems = extensionMenuRoot.getElementsByAttribute("type", "checkbox");
+
+    is(checkboxItems.length, 3, "there should be 3 checkbox items in the context menu");
+
+    is(checkboxItems[0].hasAttribute("checked"), expectedStates[0], `checkbox item 1 has state (checked=${expectedStates[0]})`);
+    is(checkboxItems[1].hasAttribute("checked"), expectedStates[1], `checkbox item 2 has state (checked=${expectedStates[1]})`);
+    is(checkboxItems[2].hasAttribute("checked"), expectedStates[2], `checkbox item 3 has state (checked=${expectedStates[2]})`);
+
+    return extensionMenuRoot.getElementsByAttribute("type", "checkbox");
+  }
+
+  let extensionMenuRoot = yield openExtensionContextMenu();
+  let items = confirmCheckboxStates(extensionMenuRoot, [false, true, false]);
+  yield closeExtensionContextMenu(items[0]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmCheckboxStates(extensionMenuRoot, [true, true, false]);
+  yield closeExtensionContextMenu(items[2]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmCheckboxStates(extensionMenuRoot, [true, true, true]);
+  yield closeExtensionContextMenu(items[0]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmCheckboxStates(extensionMenuRoot, [false, true, true]);
+  yield closeExtensionContextMenu(items[2]);
+
+  yield extension.unload();
+  yield BrowserTestUtils.removeTab(tab1);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
@@ -0,0 +1,69 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+  let encodedImageData = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
+  let decodedImageData = atob(encodedImageData);
+  const IMAGE_ARRAYBUFFER = Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+      "icons": {
+        "18": "extension.png",
+      },
+    },
+
+    files: {
+      "extension.png": IMAGE_ARRAYBUFFER,
+    },
+
+    background: function() {
+      let menuitemId = browser.contextMenus.create({
+        title: "child-to-delete",
+        onclick: () => {
+          browser.contextMenus.remove(menuitemId);
+        },
+      });
+
+      browser.contextMenus.create({
+        title: "child",
+      });
+
+      browser.test.notifyPass("contextmenus-icons");
+    },
+  });
+
+  let confirmContextMenuIcon = (rootElement) => {
+    let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/extension\.png$`);
+    let imageUrl = rootElement.getAttribute("image");
+    ok(expectedURL.test(imageUrl), "The context menu should display the extension icon next to the root element");
+  };
+
+  yield extension.startup();
+  yield extension.awaitFinish("contextmenus-icons");
+
+  let extensionMenu = yield openExtensionContextMenu();
+
+  let contextMenu = document.getElementById("contentAreaContextMenu");
+  let topLevelMenuItem = contextMenu.getElementsByAttribute("ext-type", "top-level-menu")[0];
+  confirmContextMenuIcon(topLevelMenuItem);
+
+  let childToDelete = extensionMenu.getElementsByAttribute("label", "child-to-delete")[0];
+  yield closeExtensionContextMenu(childToDelete);
+
+  yield openExtensionContextMenu();
+
+  contextMenu = document.getElementById("contentAreaContextMenu");
+  topLevelMenuItem = contextMenu.getElementsByAttribute("label", "child")[0];
+
+  confirmContextMenuIcon(topLevelMenuItem);
+  yield closeContextMenu();
+
+  yield extension.unload();
+  yield BrowserTestUtils.removeTab(tab1);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_radioGroups.js
@@ -0,0 +1,78 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+  gBrowser.selectedTab = tab1;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+    },
+
+    background: function() {
+      browser.contextMenus.create({
+        title: "radio-group-1",
+        type: "radio",
+        checked: true,
+      });
+
+      browser.contextMenus.create({
+        type: "separator",
+      });
+
+      browser.contextMenus.create({
+        title: "radio-group-2",
+        type: "radio",
+      });
+
+      browser.contextMenus.create({
+        title: "radio-group-2",
+        type: "radio",
+      });
+
+      browser.test.notifyPass("contextmenus-radio-groups");
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("contextmenus-radio-groups");
+
+  function confirmRadioGroupStates(extensionMenuRoot, expectedStates) {
+    let radioItems = extensionMenuRoot.getElementsByAttribute("type", "radio");
+    let radioGroup1 = extensionMenuRoot.getElementsByAttribute("label", "radio-group-1");
+    let radioGroup2 = extensionMenuRoot.getElementsByAttribute("label", "radio-group-2");
+
+    is(radioItems.length, 3, "there should be 3 radio items in the context menu");
+    is(radioGroup1.length, 1, "the first radio group should only have 1 radio item");
+    is(radioGroup2.length, 2, "the second radio group should only have 2 radio items");
+
+    is(radioGroup1[0].hasAttribute("checked"), expectedStates[0], `radio item 1 has state (checked=${expectedStates[0]})`);
+    is(radioGroup2[0].hasAttribute("checked"), expectedStates[1], `radio item 2 has state (checked=${expectedStates[1]})`);
+    is(radioGroup2[1].hasAttribute("checked"), expectedStates[2], `radio item 3 has state (checked=${expectedStates[2]})`);
+
+    return extensionMenuRoot.getElementsByAttribute("type", "radio");
+  }
+
+  let extensionMenuRoot = yield openExtensionContextMenu();
+  let items = confirmRadioGroupStates(extensionMenuRoot, [true, false, false]);
+  yield closeExtensionContextMenu(items[1]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmRadioGroupStates(extensionMenuRoot, [true, true, false]);
+  yield closeExtensionContextMenu(items[2]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmRadioGroupStates(extensionMenuRoot, [true, false, true]);
+  yield closeExtensionContextMenu(items[0]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmRadioGroupStates(extensionMenuRoot, [true, false, true]);
+  yield closeExtensionContextMenu(items[0]);
+
+  yield extension.unload();
+  yield BrowserTestUtils.removeTab(tab1);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_uninstall.js
@@ -0,0 +1,84 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+  // Install an extension.
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+    },
+
+    background: function() {
+      browser.contextMenus.create({title: "a"});
+      browser.contextMenus.create({title: "b"});
+      browser.test.notifyPass("contextmenus-icons");
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("contextmenus-icons");
+
+  // Open the context menu.
+  let contextMenu = yield openContextMenu("#img1");
+
+  // Confirm that the extension menu item exists.
+  let topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+  is(topLevelExtensionMenuItems.length, 1, "the top level extension menu item exists");
+
+  yield closeContextMenu();
+
+  // Uninstall the extension.
+  yield extension.unload();
+
+  // Open the context menu.
+  contextMenu = yield openContextMenu("#img1");
+
+  // Confirm that the extension menu item has been removed.
+  topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+  is(topLevelExtensionMenuItems.length, 0, "no top level extension menu items should exist");
+
+  yield closeContextMenu();
+
+  // Install a new extension.
+  extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+    },
+    background: function() {
+      browser.contextMenus.create({title: "c"});
+      browser.contextMenus.create({title: "d"});
+      browser.test.notifyPass("contextmenus-uninstall-second-extension");
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("contextmenus-uninstall-second-extension");
+
+  // Open the context menu.
+  contextMenu = yield openContextMenu("#img1");
+
+  // Confirm that only the new extension menu item is in the context menu.
+  topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+  is(topLevelExtensionMenuItems.length, 1, "only one top level extension menu item should exist");
+
+  // Close the context menu.
+  yield closeContextMenu();
+
+  // Uninstall the extension.
+  yield extension.unload();
+
+  // Open the context menu.
+  contextMenu = yield openContextMenu("#img1");
+
+  // Confirm that no extension menu items exist.
+  topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+  is(topLevelExtensionMenuItems.length, 0, "no top level extension menu items should exist");
+
+  yield closeContextMenu();
+
+  yield BrowserTestUtils.removeTab(tab1);
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -3,16 +3,18 @@
 "use strict";
 
 /* exported CustomizableUI makeWidgetId focusWindow forceGC
  *          getBrowserActionWidget
  *          clickBrowserAction clickPageAction
  *          getBrowserActionPopup getPageActionPopup
  *          closeBrowserAction closePageAction
  *          promisePopupShown promisePopupHidden
+ *          openContextMenu closeContextMenu
+ *          openExtensionContextMenu closeExtensionContextMenu
  */
 
 var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
 var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
 
 // Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable
 // times in debug builds, which results in intermittent timeouts. Until we have
 // a better solution, we force a GC after certain strategic tests, which tend to
@@ -99,16 +101,54 @@ function closeBrowserAction(extension, w
   let group = getBrowserActionWidget(extension);
 
   let node = win.document.getElementById(group.viewId);
   CustomizableUI.hidePanelForNode(node);
 
   return Promise.resolve();
 }
 
+function* openContextMenu(id) {
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+  let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+  yield BrowserTestUtils.synthesizeMouseAtCenter(id, {type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
+  yield popupShownPromise;
+  return contentAreaContextMenu;
+}
+
+function* closeContextMenu() {
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+  let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+  contentAreaContextMenu.hidePopup();
+  yield popupHiddenPromise;
+}
+
+function* openExtensionContextMenu() {
+  let contextMenu = yield openContextMenu("#img1");
+  let topLevelMenu = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+
+  // Return null if the extension only has one item and therefore no extension menu.
+  if (topLevelMenu.length == 0) {
+    return null;
+  }
+
+  let extensionMenu = topLevelMenu[0].childNodes[0];
+  let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(extensionMenu, {});
+  yield popupShownPromise;
+  return extensionMenu;
+}
+
+function* closeExtensionContextMenu(itemToSelect) {
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+  let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+  EventUtils.synthesizeMouseAtCenter(itemToSelect, {});
+  yield popupHiddenPromise;
+}
+
 function getPageActionPopup(extension, win = window) {
   let panelId = makeWidgetId(extension.id) + "-panel";
   return win.document.getElementById(panelId);
 }
 
 function clickPageAction(extension, win = window) {
   // This would normally be set automatically on navigation, and cleared
   // when the user types a value into the URL bar, to show and hide page
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -89,18 +89,16 @@ browser.jar:
 * skin/classic/browser/preferences/in-content/dialog.css      (preferences/in-content/dialog.css)
   skin/classic/browser/preferences/applications.css   (preferences/applications.css)
   skin/classic/browser/social/services-16.png         (social/services-16.png)
   skin/classic/browser/social/services-64.png         (social/services-64.png)
   skin/classic/browser/social/share-button.png        (social/share-button.png)
   skin/classic/browser/social/share-button-active.png (social/share-button-active.png)
   skin/classic/browser/tabbrowser/alltabs.png         (tabbrowser/alltabs.png)
   skin/classic/browser/tabbrowser/alltabs-inverted.png (tabbrowser/alltabs-inverted.png)
-  skin/classic/browser/tabbrowser/connecting.png      (tabbrowser/connecting.png)
-  skin/classic/browser/tabbrowser/connecting@2x.png   (tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/newtab.svg                (tabbrowser/newtab.svg)
   skin/classic/browser/tabbrowser/newtab-inverted.svg       (tabbrowser/newtab-inverted.svg)
   skin/classic/browser/tabbrowser/tab-active-middle.png     (tabbrowser/tab-active-middle.png)
   skin/classic/browser/tabbrowser/tab-active-middle@2x.png  (tabbrowser/tab-active-middle@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-left.png        (tabbrowser/tab-arrow-left.png)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
   skin/classic/browser/tabbrowser/tab-background-end.png    (tabbrowser/tab-background-end.png)
   skin/classic/browser/tabbrowser/tab-background-end@2x.png (tabbrowser/tab-background-end@2x.png)
deleted file mode 100644
index e564fb5708f3d9da4c623bb3bdfccb960566e327..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 97e2b2eb67ecc58e98dd839187143ab6870699e5..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -140,18 +140,16 @@ browser.jar:
   skin/classic/browser/social/services-64@2x.png            (social/services-64@2x.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon.png             (tabbrowser/alltabs-box-bkgnd-icon.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted.png    (tabbrowser/alltabs-box-bkgnd-icon-inverted.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png (tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png)
   skin/classic/browser/tabbrowser/newtab.png                             (tabbrowser/newtab.png)
   skin/classic/browser/tabbrowser/newtab@2x.png                          (tabbrowser/newtab@2x.png)
   skin/classic/browser/tabbrowser/newtab-inverted.png                    (tabbrowser/newtab-inverted.png)
   skin/classic/browser/tabbrowser/newtab-inverted@2x.png                 (tabbrowser/newtab-inverted@2x.png)
-  skin/classic/browser/tabbrowser/connecting.png                         (tabbrowser/connecting.png)
-  skin/classic/browser/tabbrowser/connecting@2x.png                      (tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/tab-active-middle.png                  (tabbrowser/tab-active-middle.png)
   skin/classic/browser/tabbrowser/tab-active-middle@2x.png               (tabbrowser/tab-active-middle@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-left.png                     (tabbrowser/tab-arrow-left.png)
   skin/classic/browser/tabbrowser/tab-arrow-left@2x.png                  (tabbrowser/tab-arrow-left@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png            (tabbrowser/tab-arrow-left-inverted.png)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted@2x.png         (tabbrowser/tab-arrow-left-inverted@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-right.png                    (tabbrowser/tab-arrow-right.png)
   skin/classic/browser/tabbrowser/tab-arrow-right@2x.png                 (tabbrowser/tab-arrow-right@2x.png)
deleted file mode 100644
index e564fb5708f3d9da4c623bb3bdfccb960566e327..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 97e2b2eb67ecc58e98dd839187143ab6870699e5..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -95,16 +95,18 @@
   skin/classic/browser/search-indicator-badge-add.png          (../shared/search/search-indicator-badge-add.png)
   skin/classic/browser/search-indicator-badge-add@2x.png       (../shared/search/search-indicator-badge-add@2x.png)
   skin/classic/browser/search-history-icon.svg                 (../shared/search/history-icon.svg)
   skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
   skin/classic/browser/search-arrow-go.svg                     (../shared/search/search-arrow-go.svg)
   skin/classic/browser/social/chat-icons.svg                   (../shared/social/chat-icons.svg)
   skin/classic/browser/social/gear_default.png                 (../shared/social/gear_default.png)
   skin/classic/browser/social/gear_clicked.png                 (../shared/social/gear_clicked.png)
+  skin/classic/browser/tabbrowser/connecting.png               (../shared/tabbrowser/connecting.png)
+  skin/classic/browser/tabbrowser/connecting@2x.png            (../shared/tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/crashed.svg                  (../shared/tabbrowser/crashed.svg)
   skin/classic/browser/tabbrowser/pendingpaint.png             (../shared/tabbrowser/pendingpaint.png)
 * skin/classic/browser/tabbrowser/tab-audio.svg                (../shared/tabbrowser/tab-audio.svg)
   skin/classic/browser/tabbrowser/tab-audio-small.svg          (../shared/tabbrowser/tab-audio-small.svg)
   skin/classic/browser/tabbrowser/tab-overflow-indicator.png   (../shared/tabbrowser/tab-overflow-indicator.png)
   skin/classic/browser/theme-switcher-icon.png                 (../shared/theme-switcher-icon.png)
   skin/classic/browser/theme-switcher-icon@2x.png              (../shared/theme-switcher-icon@2x.png)
   skin/classic/browser/translating-16.png                      (../shared/translation/translating-16.png)
rename from browser/themes/windows/tabbrowser/connecting.png
rename to browser/themes/shared/tabbrowser/connecting.png
rename from browser/themes/windows/tabbrowser/connecting@2x.png
rename to browser/themes/shared/tabbrowser/connecting@2x.png
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -148,18 +148,16 @@ browser.jar:
 * skin/classic/browser/preferences/in-content/dialog.css       (preferences/in-content/dialog.css)
   skin/classic/browser/preferences/applications.css            (preferences/applications.css)
   skin/classic/browser/social/services-16.png                  (social/services-16.png)
   skin/classic/browser/social/services-64.png                  (social/services-64.png)
   skin/classic/browser/tabbrowser/newtab.svg                   (tabbrowser/newtab.svg)
   skin/classic/browser/tabbrowser/newtab-XPVista7.svg          (tabbrowser/newtab-XPVista7.svg)
   skin/classic/browser/tabbrowser/newtab-inverted.svg          (tabbrowser/newtab-inverted.svg)
   skin/classic/browser/tabbrowser/newtab-inverted-XPVista7.svg (tabbrowser/newtab-inverted-XPVista7.svg)
-  skin/classic/browser/tabbrowser/connecting.png               (tabbrowser/connecting.png)
-  skin/classic/browser/tabbrowser/connecting@2x.png            (tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/tab-active-middle.png        (tabbrowser/tab-active-middle.png)
   skin/classic/browser/tabbrowser/tab-active-middle@2x.png     (tabbrowser/tab-active-middle@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-left.svg           (tabbrowser/tab-arrow-left.svg)
   skin/classic/browser/tabbrowser/tab-arrow-left-XPVista7.svg  (tabbrowser/tab-arrow-left-XPVista7.svg)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted.svg  (tabbrowser/tab-arrow-left-inverted.svg)
   skin/classic/browser/tabbrowser/tab-background-start.png     (tabbrowser/tab-background-start.png)
   skin/classic/browser/tabbrowser/tab-background-start@2x.png  (tabbrowser/tab-background-start@2x.png)
   skin/classic/browser/tabbrowser/tab-background-middle.png    (tabbrowser/tab-background-middle.png)
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -332,16 +332,32 @@ nsDOMWindowUtils::UpdateLayerTree()
       presShell->Paint(view, view->GetBounds(),
           nsIPresShell::PAINT_LAYERS | nsIPresShell::PAINT_SYNC_DECODE_IMAGES);
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::GetContentViewerSize(uint32_t *aDisplayWidth, uint32_t *aDisplayHeight)
+{
+  nsIPresShell* presShell = GetPresShell();
+  LayoutDeviceIntSize displaySize;
+
+  if (!presShell || !nsLayoutUtils::GetContentViewerSize(presShell->GetPresContext(), displaySize)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *aDisplayWidth = displaySize.width;
+  *aDisplayHeight = displaySize.height;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::GetViewportInfo(uint32_t aDisplayWidth,
                                   uint32_t aDisplayHeight,
                                   double *aDefaultZoom, bool *aAllowZoom,
                                   double *aMinZoom, double *aMaxZoom,
                                   uint32_t *aWidth, uint32_t *aHeight,
                                   bool *aAutoSize)
 {
   nsIDocument* doc = GetDocument();
@@ -535,24 +551,27 @@ nsDOMWindowUtils::SetResolutionAndScaleT
   }
 
   presShell->SetResolutionAndScaleTo(aResolution);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::SetRestoreResolution(float aResolution)
+nsDOMWindowUtils::SetRestoreResolution(float aResolution,
+                                       uint32_t aDisplayWidth,
+                                       uint32_t aDisplayHeight)
 {
   nsIPresShell* presShell = GetPresShell();
   if (!presShell) {
     return NS_ERROR_FAILURE;
   }
 
-  presShell->SetRestoreResolution(aResolution);
+  presShell->SetRestoreResolution(aResolution,
+    LayoutDeviceIntSize(aDisplayWidth, aDisplayHeight));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetResolution(float* aResolution)
 {
   nsIPresShell* presShell = GetPresShell();
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -117,16 +117,21 @@ interface nsIDOMWindowUtils : nsISupport
    */
   void getViewportInfo(in uint32_t aDisplayWidth, in uint32_t aDisplayHeight,
                        out double aDefaultZoom, out boolean aAllowZoom,
                        out double aMinZoom, out double aMaxZoom,
                        out uint32_t aWidth, out uint32_t aHeight,
                        out boolean aAutoSize);
 
   /**
+   * Information about the window size in device pixels.
+   */
+  void getContentViewerSize(out uint32_t aDisplayWidth, out uint32_t aDisplayHeight);
+
+  /**
    * For any scrollable element, this allows you to override the
    * visible region and draw more than what is visible, which is
    * useful for asynchronous drawing. The "displayport" will be
    * <xPx, yPx, widthPx, heightPx> in units of CSS pixels,
    * regardless of the size of the enclosing container.  This
    * will *not* trigger reflow.
    *
    * For the root scroll area, pass in the root document element.
@@ -219,20 +224,25 @@ interface nsIDOMWindowUtils : nsISupport
    * for pinch-zoom on mobile platforms.
    *
    * The caller of this method must have chrome privileges.
    */
   void setResolutionAndScaleTo(in float aResolution);
 
   /**
    * Set a resolution on the presShell which is the "restored" from history.
+   * The display dimensions are compared to their current values and used
+   * to scale the resolution value if necessary, e.g. if the device was
+   * rotated between saving and restoring of the session data.
    * This resolution should be used when painting for the first time. Calling
    * this too late may have no effect.
    */
-  void setRestoreResolution(in float aResolution);
+  void setRestoreResolution(in float aResolution,
+                            in uint32_t aDisplayWidth,
+                            in uint32_t aDisplayHeight);
 
   /**
    * Whether the resolution has been set by the user.
    * This gives a way to check whether the provided resolution is the default
    * value or restored from a previous session.
    *
    * Can only be accessed with chrome privileges.
    */
--- a/layout/base/MobileViewportManager.cpp
+++ b/layout/base/MobileViewportManager.cpp
@@ -80,19 +80,23 @@ MobileViewportManager::Destroy()
     observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data());
   }
 
   mDocument = nullptr;
   mPresShell = nullptr;
 }
 
 void
-MobileViewportManager::SetRestoreResolution(float aResolution)
+MobileViewportManager::SetRestoreResolution(float aResolution,
+                                            LayoutDeviceIntSize aDisplaySize)
 {
   mRestoreResolution = Some(aResolution);
+  ScreenIntSize restoreDisplaySize = ViewAs<ScreenPixel>(aDisplaySize,
+    PixelCastJustification::LayoutDeviceIsScreenForBounds);
+  mRestoreDisplaySize = Some(restoreDisplaySize);
 }
 
 void
 MobileViewportManager::RequestReflow()
 {
   MVM_LOG("%p: got a reflow request\n", this);
   RefreshViewportSize(false);
 }
@@ -162,30 +166,57 @@ MobileViewportManager::ClampZoom(const C
   }
   if (zoom > aViewportInfo.GetMaxZoom()) {
     zoom = aViewportInfo.GetMaxZoom();
     MVM_LOG("%p: Clamped to %f\n", this, zoom.scale);
   }
   return zoom;
 }
 
+LayoutDeviceToLayerScale
+MobileViewportManager::ScaleResolutionWithDisplayWidth(const LayoutDeviceToLayerScale& aRes,
+                                                       const float& aDisplayWidthChangeRatio,
+                                                       const CSSSize& aNewViewport,
+                                                       const CSSSize& aOldViewport)
+{
+  float cssViewportChangeRatio = (aOldViewport.width == 0)
+     ? 1.0f : aNewViewport.width / aOldViewport.width;
+  LayoutDeviceToLayerScale newRes(aRes.scale * aDisplayWidthChangeRatio
+    / cssViewportChangeRatio);
+  MVM_LOG("%p: Old resolution was %f, changed by %f/%f to %f\n", this, aRes.scale,
+    aDisplayWidthChangeRatio, cssViewportChangeRatio, newRes.scale);
+  return newRes;
+}
+
 CSSToScreenScale
 MobileViewportManager::UpdateResolution(const nsViewportInfo& aViewportInfo,
                                         const ScreenIntSize& aDisplaySize,
                                         const CSSSize& aViewport,
                                         const Maybe<float>& aDisplayWidthChangeRatio)
 {
   CSSToLayoutDeviceScale cssToDev =
       mPresShell->GetPresContext()->CSSToDevPixelScale();
   LayoutDeviceToLayerScale res(mPresShell->GetResolution());
 
   if (mIsFirstPaint) {
     CSSToScreenScale defaultZoom;
     if (mRestoreResolution) {
-      defaultZoom = CSSToScreenScale(mRestoreResolution.value() * cssToDev.scale);
+    LayoutDeviceToLayerScale restoreResolution(mRestoreResolution.value());
+      if (mRestoreDisplaySize) {
+        CSSSize prevViewport = mDocument->GetViewportInfo(mRestoreDisplaySize.value()).GetSize();
+        float restoreDisplayWidthChangeRatio = (mRestoreDisplaySize.value().width > 0)
+          ? (float)aDisplaySize.width / (float)mRestoreDisplaySize.value().width : 1.0f;
+
+        restoreResolution =
+          ScaleResolutionWithDisplayWidth(restoreResolution,
+                                          restoreDisplayWidthChangeRatio,
+                                          aViewport,
+                                          prevViewport);
+      }
+      defaultZoom = CSSToScreenScale(restoreResolution.scale * cssToDev.scale);
       MVM_LOG("%p: restored zoom is %f\n", this, defaultZoom.scale);
       defaultZoom = ClampZoom(defaultZoom, aViewportInfo);
     } else {
       defaultZoom = aViewportInfo.GetDefaultZoom();
       MVM_LOG("%p: default zoom from viewport is %f\n", this, defaultZoom.scale);
       if (!aViewportInfo.IsDefaultZoomValid()) {
         defaultZoom = MaxScaleRatio(ScreenSize(aDisplaySize), aViewport);
         MVM_LOG("%p: Intrinsic computed zoom is %f\n", this, defaultZoom.scale);
@@ -226,24 +257,19 @@ MobileViewportManager::UpdateResolution(
   // 1. screen size changes, CSS viewport does not (pages with no meta viewport
   //    or a fixed size viewport)
   // 2. screen size changes, CSS viewport also does (pages with a device-width
   //    viewport)
   // 3. screen size remains constant, but CSS viewport changes (meta viewport
   //    tag is added or removed)
   // 4. neither screen size nor CSS viewport changes
   if (aDisplayWidthChangeRatio) {
-    float cssViewportChangeRatio = (mMobileViewportSize.width == 0)
-       ? 1.0f : aViewport.width / mMobileViewportSize.width;
-    LayoutDeviceToLayerScale newRes(res.scale * aDisplayWidthChangeRatio.value()
-      / cssViewportChangeRatio);
-    MVM_LOG("%p: Old resolution was %f, changed by %f/%f to %f\n", this, res.scale,
-      aDisplayWidthChangeRatio.value(), cssViewportChangeRatio, newRes.scale);
-    mPresShell->SetResolutionAndScaleTo(newRes.scale);
-    res = newRes;
+    res = ScaleResolutionWithDisplayWidth(res, aDisplayWidthChangeRatio.value(),
+      aViewport, mMobileViewportSize);
+    mPresShell->SetResolutionAndScaleTo(res.scale);
   }
 
   return ViewTargetAs<ScreenPixel>(cssToDev * res / ParentLayerToLayerScale(1),
     PixelCastJustification::ScreenIsParentLayerForRoot);
 }
 
 void
 MobileViewportManager::UpdateSPCSPS(const ScreenIntSize& aDisplaySize,
--- a/layout/base/MobileViewportManager.h
+++ b/layout/base/MobileViewportManager.h
@@ -24,18 +24,22 @@ public:
   NS_DECL_NSIOBSERVER
 
   MobileViewportManager(nsIPresShell* aPresShell,
                         nsIDocument* aDocument);
   void Destroy();
 
   /* Provide a resolution to use during the first paint instead of the default
    * resolution computed from the viewport info metadata. This is in the same
-   * "units" as the argument to nsDOMWindowUtils::SetResolutionAndScaleTo. */
-  void SetRestoreResolution(float aResolution);
+   * "units" as the argument to nsDOMWindowUtils::SetResolutionAndScaleTo.
+   * Also takes the previous display dimensions as they were at the time the
+   * resolution was stored in order to correctly adjust the resolution if the
+   * device was rotated in the meantime. */
+  void SetRestoreResolution(float aResolution,
+                            mozilla::LayoutDeviceIntSize aDisplaySize);
 
   /* Notify the MobileViewportManager that a reflow was requested in the
    * presShell.*/
   void RequestReflow();
 
   /* Notify the MobileViewportManager that the resolution on the presShell was
    * updated, and the SPCSPS needs to be updated. */
   void ResolutionUpdated();
@@ -53,31 +57,41 @@ private:
 
   /* Secondary main helper method to update just the SPCSPS. */
   void RefreshSPCSPS();
 
   /* Helper to clamp the given zoom by the min/max in the viewport info. */
   mozilla::CSSToScreenScale ClampZoom(const mozilla::CSSToScreenScale& aZoom,
                                       const nsViewportInfo& aViewportInfo);
 
+  /* Helper to update the given resolution according to changed display and viewport widths. */
+  mozilla::LayoutDeviceToLayerScale
+  ScaleResolutionWithDisplayWidth(const mozilla::LayoutDeviceToLayerScale& aRes,
+                                  const float& aDisplayWidthChangeRatio,
+                                  const mozilla::CSSSize& aNewViewport,
+                                  const mozilla::CSSSize& aOldViewport);
+
   /* Updates the presShell resolution and returns the new zoom. */
   mozilla::CSSToScreenScale UpdateResolution(const nsViewportInfo& aViewportInfo,
                                              const mozilla::ScreenIntSize& aDisplaySize,
                                              const mozilla::CSSSize& aViewport,
                                              const mozilla::Maybe<float>& aDisplayWidthChangeRatio);
+
   /* Updates the scroll-position-clamping scrollport size */
   void UpdateSPCSPS(const mozilla::ScreenIntSize& aDisplaySize,
                     const mozilla::CSSToScreenScale& aZoom);
+
   /* Updates the displayport margins for the presShell's root scrollable frame */
   void UpdateDisplayPortMargins();
 
   nsCOMPtr<nsIDocument> mDocument;
   nsIPresShell* MOZ_NON_OWNING_REF mPresShell; // raw ref since the presShell owns this
   nsCOMPtr<nsIDOMEventTarget> mEventTarget;
   bool mIsFirstPaint;
   bool mPainted;
   mozilla::LayoutDeviceIntSize mDisplaySize;
   mozilla::CSSSize mMobileViewportSize;
   mozilla::Maybe<float> mRestoreResolution;
+  mozilla::Maybe<mozilla::ScreenIntSize> mRestoreDisplaySize;
 };
 
 #endif
 
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -43,16 +43,17 @@
 #include "nsWeakReference.h"
 #include <stdio.h> // for FILE definition
 #include "nsChangeHint.h"
 #include "nsRefPtrHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsPresArena.h"
 #include "nsMargin.h"
 #include "nsFrameState.h"
+#include "Units.h"
 #include "Visibility.h"
 
 #ifdef MOZ_B2G
 #include "nsIHardwareKeyHandler.h"
 #endif
 
 class nsDocShell;
 class nsIDocument;
@@ -1441,17 +1442,18 @@ public:
    * SetResolutionAndScaleTo(), and set to false by a call to SetResolution().
    */
   virtual bool ScaleToResolution() const = 0;
 
   /**
    * Used by session restore code to restore a resolution before the first
    * paint.
    */
-  virtual void SetRestoreResolution(float aResolution) = 0;
+  virtual void SetRestoreResolution(float aResolution,
+                                    mozilla::LayoutDeviceIntSize aDisplaySize) = 0;
 
   /**
    * Returns whether we are in a DrawWindow() call that used the
    * DRAWWINDOW_DO_NOT_FLUSH flag.
    */
   bool InDrawWindowNotFlushing() const
   { return mRenderFlags & STATE_DRAWWINDOW_NOT_FLUSHING; }
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5628,20 +5628,21 @@ float PresShell::GetCumulativeNonRootSca
       currentShell = parentCtx->PresShell();
     } else {
       currentShell = nullptr;
     }
   }
   return resolution;
 }
 
-void PresShell::SetRestoreResolution(float aResolution)
+void PresShell::SetRestoreResolution(float aResolution,
+                                     LayoutDeviceIntSize aDisplaySize)
 {
   if (mMobileViewportManager) {
-    mMobileViewportManager->SetRestoreResolution(aResolution);
+    mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize);
   }
 }
 
 void PresShell::SetRenderingState(const RenderingState& aState)
 {
   if (mRenderFlags != aState.mRenderFlags) {
     // Rendering state changed in a way that forces us to flush any
     // retained layers we might already have.
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -235,17 +235,18 @@ public:
     return SetResolutionImpl(aResolution, /* aScaleToResolution = */ false);
   }
   virtual nsresult SetResolutionAndScaleTo(float aResolution) override {
     return SetResolutionImpl(aResolution, /* aScaleToResolution = */ true);
   }
   virtual bool ScaleToResolution() const override;
   virtual float GetCumulativeResolution() override;
   virtual float GetCumulativeNonRootScaleResolution() override;
-  virtual void SetRestoreResolution(float aResolution) override;
+  virtual void SetRestoreResolution(float aResolution,
+                                    mozilla::LayoutDeviceIntSize aDisplaySize) override;
 
   //nsIViewObserver interface
 
   virtual void Paint(nsView* aViewToPaint, const nsRegion& aDirtyRegion,
                      uint32_t aFlags) override;
   virtual nsresult HandleEvent(nsIFrame* aFrame,
                                mozilla::WidgetGUIEvent* aEvent,
                                bool aDontRetargetEvents,
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -748,47 +748,37 @@ SessionStore.prototype = {
     content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
       Ci.nsIDOMWindowUtils).getResolution(zoom);
     scrolldata.zoom = {};
     scrolldata.zoom.resolution = zoom.value;
     log("onTabScroll() zoom level: " + zoom.value);
 
     // Save some data that'll help in adjusting the zoom level
     // when restoring in a different screen orientation.
-    let viewportInfo = this._getViewportInfo(aWindow.outerWidth, aWindow.outerHeight, content);
-    scrolldata.zoom.autoSize = viewportInfo.autoSize;
-    log("onTabScroll() autoSize: " + scrolldata.zoom.autoSize);
-    scrolldata.zoom.windowWidth = aWindow.outerWidth;
-    log("onTabScroll() windowWidth: " + scrolldata.zoom.windowWidth);
+    scrolldata.zoom.displaySize = this._getContentViewerSize(content);
+    log("onTabScroll() displayWidth: " + scrolldata.zoom.displaySize.width);
 
     // Save zoom and scroll data.
     data.scrolldata = scrolldata;
     log("onTabScroll() ran for tab " + aWindow.BrowserApp.getTabForBrowser(aBrowser).id);
     let evt = new Event("SSTabScrollCaptured", {"bubbles":true, "cancelable":false});
     aBrowser.dispatchEvent(evt);
     this.saveStateDelayed();
   },
 
-  _getViewportInfo: function ss_getViewportInfo(aDisplayWidth, aDisplayHeight, aWindow) {
-    let viewportInfo = {};
-    let defaultZoom = {}, allowZoom = {}, minZoom = {}, maxZoom ={},
-        width = {}, height = {}, autoSize = {};
+  _getContentViewerSize: function ss_getContentViewerSize(aWindow) {
+    let displaySize = {};
+    let width = {}, height = {};
     aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
-      Ci.nsIDOMWindowUtils).getViewportInfo(aDisplayWidth, aDisplayHeight,
-        defaultZoom, allowZoom, minZoom, maxZoom, width, height, autoSize);
+      Ci.nsIDOMWindowUtils).getContentViewerSize(width, height);
 
-    viewportInfo.defaultZoom = defaultZoom.value;
-    viewportInfo.allowZoom = allowZoom.value;
-    viewportInfo.minZoom = maxZoom.value;
-    viewportInfo.maxZoom = maxZoom.value;
-    viewportInfo.width = width.value;
-    viewportInfo.height = height.value;
-    viewportInfo.autoSize = autoSize.value;
+    displaySize.width = width.value;
+    displaySize.height = height.value;
 
-    return viewportInfo;
+    return displaySize;
   },
 
   saveStateDelayed: function ss_saveStateDelayed() {
     if (!this._saveTimer) {
       // Interval until the next disk operation is allowed
       let minimalDelay = this._lastSaveTime + this._interval - Date.now();
 
       // If we have to wait, set a timer, otherwise saveState directly
@@ -1380,51 +1370,34 @@ SessionStore.prototype = {
   _restoreTextData: function ss_restoreTextData(aFormData, aBrowser) {
     if (aFormData) {
       log("_restoreTextData()");
       FormData.restoreTree(aBrowser.contentWindow, aFormData);
     }
   },
 
   /**
-  * Restores the zoom level of the window. This needs to be called before
-  * first paint/load (whichever comes first) to take any effect.
-  */
+   * Restores the zoom level of the window. This needs to be called before
+   * first paint/load (whichever comes first) to take any effect.
+   */
   _restoreZoom: function ss_restoreZoom(aScrollData, aBrowser) {
-    if (aScrollData && aScrollData.zoom) {
-      let recalculatedZoom = this._recalculateZoom(aScrollData.zoom);
-      log("_restoreZoom(), resolution: " + recalculatedZoom);
+    if (aScrollData && aScrollData.zoom && aScrollData.zoom.displaySize) {
+      log("_restoreZoom(), resolution: " + aScrollData.zoom.resolution +
+          ", old displayWidth: " + aScrollData.zoom.displaySize.width);
 
       let utils = aBrowser.contentWindow.QueryInterface(
         Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
       // Restore zoom level.
-      utils.setRestoreResolution(recalculatedZoom);
+      utils.setRestoreResolution(aScrollData.zoom.resolution,
+                                 aScrollData.zoom.displaySize.width,
+                                 aScrollData.zoom.displaySize.height);
     }
   },
 
   /**
-  * Recalculates the zoom level to account for a changed display width,
-  * e.g. because the device was rotated.
-  */
-  _recalculateZoom: function ss_recalculateZoom(aZoomData) {
-    let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
-
-    // Pages with "width=device-width" won't need any zoom level scaling.
-    if (!aZoomData.autoSize) {
-      let oldWidth = aZoomData.windowWidth;
-      let newWidth = browserWin.outerWidth;
-      if (oldWidth != newWidth && oldWidth > 0 && newWidth > 0) {
-        log("_recalculateZoom(), old resolution: " + aZoomData.resolution);
-        return newWidth / oldWidth * aZoomData.resolution;
-      }
-    }
-    return aZoomData.resolution;
-  },
-
-  /**
   * Takes serialized scroll positions and restores them into the given browser.
   */
   _restoreScrollPosition: function ss_restoreScrollPosition(aScrollData, aBrowser) {
     if (aScrollData) {
       log("_restoreScrollPosition()");
       ScrollPosition.restoreTree(aBrowser.contentWindow, aScrollData);
     }
   },
--- a/mobile/android/tests/browser/chrome/test_session_scroll_position.html
+++ b/mobile/android/tests/browser/chrome/test_session_scroll_position.html
@@ -27,16 +27,18 @@ https://bugzilla.mozilla.org/show_bug.cg
   // The chrome window.
   let chromeWin;
 
   // Track the tabs where the tests are happening.
   let tabScroll;
 
   // Use something with enough content to allow for scrolling.
   const URL = "http://example.org/chrome/mobile/android/tests/browser/chrome/basic_article_mobile.html";
+  // Something to test the zoom level scaling on rotation with.
+  const URL_desktop = "http://example.org/chrome/mobile/android/tests/browser/chrome/basic_article.html";
 
   function dispatchUIEvent(browser, type) {
     let event = browser.contentDocument.createEvent("UIEvents");
     event.initUIEvent(type, true, false, browser.contentDocument.defaultView, 0);
     browser.dispatchEvent(event);
   }
 
   function setScrollPosition(browser, x, y) {
@@ -164,16 +166,84 @@ https://bugzilla.mozilla.org/show_bug.cg
     ok(fuzzyEquals(zoom.value, ZOOM), "zoom restored correctly");
     is(scrollX.value, SCROLL_X, "scrollX restored correctly");
     is(scrollY.value, SCROLL_Y, "scrollY restored correctly");
 
     // Remove the tab.
     BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
   });
 
+  add_task(function* test_sessionStoreZoomLevelRecalc() {
+    const ZOOM = 4.2;
+    const SCROLL_X = 42;
+    const SCROLL_Y = 42;
+
+    chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+    let BrowserApp = chromeWin.BrowserApp;
+
+    // Creates a tab, sets a scroll position and zoom level and closes the tab.
+    function createAndRemoveTab() {
+      return Task.spawn(function () {
+        // Create a new tab.
+        tabScroll = BrowserApp.addTab(URL_desktop);
+        let browser = tabScroll.browser;
+        yield promiseBrowserEvent(browser, "pageshow");
+
+        // Modify scroll position and zoom level.
+        setZoomLevel(browser, ZOOM);
+        setScrollPosition(browser, SCROLL_X, SCROLL_Y);
+        yield promiseTabEvent(browser, "SSTabScrollCaptured");
+
+        // Check that we've actually scrolled and zoomed.
+        let ifreq = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor);
+        let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils);
+        let scrollX = {}, scrollY = {}, zoom = {};
+        utils.getResolution(zoom);
+        utils.getScrollXY(false, scrollX, scrollY);
+        ok(fuzzyEquals(zoom.value, ZOOM), "zoom set correctly");
+        is(scrollX.value, SCROLL_X, "scrollX set correctly");
+        is(scrollY.value, SCROLL_Y, "scrollY set correctly");
+
+        // Remove the tab.
+        BrowserApp.closeTab(tabScroll);
+        yield promiseTabEvent(browser, "SSTabCloseProcessed");
+      });
+    }
+
+    yield createAndRemoveTab();
+    let state = ss.getClosedTabs(chromeWin);
+    let [{scrolldata}] = state;
+    is(scrolldata.scroll, SCROLL_X + "," + SCROLL_Y, "stored scroll position is correct");
+    ok(fuzzyEquals(scrolldata.zoom.resolution, ZOOM), "stored zoom level is correct");
+
+    // Pretend the zoom level was originally saved on a rotated device.
+    let closedTabData = ss.getClosedTabs(chromeWin)[0];
+    let displayWidth = scrolldata.zoom.displaySize.width;
+    let displayHeight = scrolldata.zoom.displaySize.height;
+    closedTabData.scrolldata.zoom.displaySize.width = displayHeight;
+    closedTabData.scrolldata.zoom.displaySize.height = displayWidth;
+
+    // Restore the closed tab.
+    let browser = ss.undoCloseTab(chromeWin, closedTabData);
+    yield promiseBrowserEvent(browser, "pageshow");
+
+    // Check the scroll position and zoom level.
+    let ifreq = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor);
+    let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils);
+    let scrollX = {}, scrollY = {}, zoom = {};
+    utils.getResolution(zoom);
+    utils.getScrollXY(false, scrollX, scrollY);
+    ok(fuzzyEquals(zoom.value, ZOOM * displayWidth / displayHeight), "recalculated zoom restored correctly");
+    is(scrollX.value, SCROLL_X, "scrollX restored correctly");
+    is(scrollY.value, SCROLL_Y, "scrollY restored correctly");
+
+    // Remove the tab.
+    BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
+  });
+
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=810981">Mozilla Bug 810981</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>