Bug 1080055: When recreating a browser restore the isAppTab attribute on the docshell. r=mconley.
authorDave Townsend <dtownsend@oxymoronical.com>
Tue, 21 Oct 2014 16:44:26 -0700
changeset 211787 01bc61be7ad2479b89f1f2af4e891092d5c3137d
parent 211616 1af0f51fc2e150568c0216e0672f9bbbdbbb1977
child 211788 1f00a7b2638713cad68277c6da90e7785ce6da54
push id27688
push userryanvm@gmail.com
push dateWed, 22 Oct 2014 20:28:54 +0000
treeherdermozilla-central@7e2fb51abd4b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1080055
milestone36.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 1080055: When recreating a browser restore the isAppTab attribute on the docshell. r=mconley.
browser/base/content/tabbrowser.xml
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_restore_isAppTab.js
browser/base/content/test/general/head.js
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1480,20 +1480,22 @@
             // Tearing down the browser gives a new permanentKey but we want to
             // keep the old one. Re-set it explicitly after unbinding from DOM.
             aBrowser.permanentKey = permanentKey;
             parent.appendChild(aBrowser);
 
             // Restore the progress listener.
             aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
 
-            if (aShouldBeRemote)
+            if (aShouldBeRemote) {
               tab.setAttribute("remote", "true");
-            else
+            } else {
               tab.removeAttribute("remote");
+              aBrowser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned })
+            }
 
             if (wasActive)
               aBrowser.focus();
 
             return true;
           ]]>
         </body>
       </method>
@@ -3094,16 +3096,23 @@
             case "DOMWebNotificationClicked": {
               let tab = this.getTabForBrowser(browser);
               if (!tab)
                 return;
               this.selectedTab = tab;
               window.focus();
               break;
             }
+            case "Browser:Init": {
+              let tab = this.getTabForBrowser(browser);
+              if (!tab)
+                return;
+              browser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned })
+              break;
+            }
           }
         ]]></body>
       </method>
 
       <constructor>
         <![CDATA[
           let browserStack = document.getAnonymousElementByAttribute(this, "anonid", "browserStack");
           this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
@@ -3151,16 +3160,17 @@
           let remote = window.QueryInterface(Ci.nsIInterfaceRequestor)
             .getInterface(Ci.nsIWebNavigation)
             .QueryInterface(Ci.nsILoadContext)
             .useRemoteTabs;
           if (remote) {
             messageManager.addMessageListener("DOMTitleChanged", this);
             messageManager.addMessageListener("DOMWindowClose", this);
             messageManager.addMessageListener("contextmenu", this);
+            messageManager.addMessageListener("Browser:Init", this);
 
             // If this window has remote tabs, switch to our tabpanels fork
             // which does asynchronous tab switching.
             this.mPanelContainer.classList.add("tabbrowser-tabpanels");
           }
           messageManager.addMessageListener("DOMWebNotificationClicked", this);
         ]]>
       </constructor>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -389,16 +389,17 @@ skip-if = buildapp == 'mulet' || e10s # 
 [browser_private_browsing_window.js]
 skip-if = buildapp == 'mulet'
 [browser_private_no_prompt.js]
 skip-if = buildapp == 'mulet'
 [browser_relatedTabs.js]
 [browser_removeTabsToTheEnd.js]
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 skip-if = e10s
+[browser_restore_isAppTab.js]
 [browser_sanitize-download-history.js]
 skip-if = true # bug 432425
 [browser_sanitize-passwordDisabledHosts.js]
 [browser_sanitize-sitepermissions.js]
 [browser_sanitize-timespans.js]
 skip-if = buildapp == 'mulet'
 [browser_sanitizeDialog.js]
 skip-if = buildapp == 'mulet'
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_restore_isAppTab.js
@@ -0,0 +1,154 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const DUMMY = "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
+
+function getMinidumpDirectory() {
+  let dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
+  dir.append("minidumps");
+  return dir;
+}
+
+// This observer is needed so we can clean up all evidence of the crash so
+// the testrunner thinks things are peachy.
+let CrashObserver = {
+  observe: function(subject, topic, data) {
+    is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
+    ok(subject instanceof Ci.nsIPropertyBag2,
+       'Subject implements nsIPropertyBag2.');
+    // we might see this called as the process terminates due to previous tests.
+    // We are only looking for "abnormal" exits...
+    if (!subject.hasKey("abnormal")) {
+      info("This is a normal termination and isn't the one we are looking for...");
+      return;
+    }
+
+    let dumpID;
+    if ('nsICrashReporter' in Ci) {
+      dumpID = subject.getPropertyAsAString('dumpID');
+      ok(dumpID, "dumpID is present and not an empty string");
+    }
+
+    if (dumpID) {
+      let minidumpDirectory = getMinidumpDirectory();
+      let file = minidumpDirectory.clone();
+      file.append(dumpID + '.dmp');
+      file.remove(true);
+      file = minidumpDirectory.clone();
+      file.append(dumpID + '.extra');
+      file.remove(true);
+    }
+  }
+}
+Services.obs.addObserver(CrashObserver, 'ipc:content-shutdown', false);
+
+registerCleanupFunction(() => {
+  Services.obs.removeObserver(CrashObserver, 'ipc:content-shutdown');
+});
+
+function frameScript() {
+  addMessageListener("Test:GetIsAppTab", function() {
+    sendAsyncMessage("Test:IsAppTab", { isAppTab: docShell.isAppTab });
+  });
+
+  addMessageListener("Test:Crash", function() {
+    privateNoteIntentionalCrash();
+    Components.utils.import("resource://gre/modules/ctypes.jsm");
+    let zero = new ctypes.intptr_t(8);
+    let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+    badptr.contents
+  });
+}
+
+function loadFrameScript(browser) {
+  browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+}
+
+function isBrowserAppTab(browser) {
+  return new Promise(resolve => {
+    function listener({ data }) {
+      browser.messageManager.removeMessageListener("Test:IsAppTab", listener);
+      resolve(data.isAppTab);
+    }
+    browser.messageManager.addMessageListener("Test:IsAppTab", listener);
+    browser.messageManager.sendAsyncMessage("Test:GetIsAppTab");
+  });
+}
+
+// Restarts the child process by crashing it then reloading the tab
+let restart = Task.async(function*(browser) {
+  // If the tab isn't remote this would crash the main process so skip it
+  if (!browser.isRemoteBrowser)
+    return browser;
+
+  // Make sure the main process has all of the current tab state before crashing
+  TabState.flush(browser);
+
+  browser.messageManager.sendAsyncMessage("Test:Crash");
+  yield promiseWaitForEvent(browser, "AboutTabCrashedLoad", false, true);
+
+  let tab = gBrowser.getTabForBrowser(browser);
+  SessionStore.reviveCrashedTab(tab);
+
+  yield promiseTabLoaded(tab);
+});
+
+add_task(function* navigate() {
+  let tab = gBrowser.addTab("about:robots");
+  let browser = tab.linkedBrowser;
+  gBrowser.selectedTab = tab;
+  yield waitForDocLoadComplete();
+  loadFrameScript(browser);
+  let isAppTab = yield isBrowserAppTab(browser);
+  ok(!isAppTab, "Docshell shouldn't think it is an app tab");
+
+  gBrowser.pinTab(tab);
+  isAppTab = yield isBrowserAppTab(browser);
+  ok(isAppTab, "Docshell should think it is an app tab");
+
+  gBrowser.loadURI(DUMMY);
+  yield waitForDocLoadComplete();
+  loadFrameScript(browser);
+  isAppTab = yield isBrowserAppTab(browser);
+  ok(isAppTab, "Docshell should think it is an app tab");
+
+  gBrowser.unpinTab(tab);
+  isAppTab = yield isBrowserAppTab(browser);
+  ok(!isAppTab, "Docshell shouldn't think it is an app tab");
+
+  gBrowser.pinTab(tab);
+  isAppTab = yield isBrowserAppTab(browser);
+  ok(isAppTab, "Docshell should think it is an app tab");
+
+  gBrowser.loadURI("about:robots");
+  yield waitForDocLoadComplete();
+  loadFrameScript(browser);
+  isAppTab = yield isBrowserAppTab(browser);
+  ok(isAppTab, "Docshell should think it is an app tab");
+
+  gBrowser.removeCurrentTab();
+});
+
+add_task(function* crash() {
+  if (!gMultiProcessBrowser || !("nsICrashReporter" in Ci))
+    return;
+
+  let tab = gBrowser.addTab(DUMMY);
+  let browser = tab.linkedBrowser;
+  gBrowser.selectedTab = tab;
+  yield waitForDocLoadComplete();
+  loadFrameScript(browser);
+  let isAppTab = yield isBrowserAppTab(browser);
+  ok(!isAppTab, "Docshell shouldn't think it is an app tab");
+
+  gBrowser.pinTab(tab);
+  isAppTab = yield isBrowserAppTab(browser);
+  ok(isAppTab, "Docshell should think it is an app tab");
+
+  yield restart(browser);
+  loadFrameScript(browser);
+  isAppTab = yield isBrowserAppTab(browser);
+  ok(isAppTab, "Docshell should think it is an app tab");
+
+  gBrowser.removeCurrentTab();
+});
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -103,26 +103,26 @@ function waitForCondition(condition, nex
 }
 
 function promiseWaitForCondition(aConditionFn) {
   let deferred = Promise.defer();
   waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass.");
   return deferred.promise;
 }
 
-function promiseWaitForEvent(object, eventName, capturing = false) {
+function promiseWaitForEvent(object, eventName, capturing = false, chrome = false) {
   return new Promise((resolve) => {
     function listener(event) {
       info("Saw " + eventName);
-      object.removeEventListener(eventName, listener, capturing);
+      object.removeEventListener(eventName, listener, capturing, chrome);
       resolve(event);
     }
 
     info("Waiting for " + eventName);
-    object.addEventListener(eventName, listener, capturing);
+    object.addEventListener(eventName, listener, capturing, chrome);
   });
 }
 
 function getTestPlugin(aName) {
   var pluginName = aName || "Test Plug-in";
   var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   var tags = ph.getPluginTags();