Backed out changeset 1917953b1f89 (bug 961867) on a CLOSED TREE r=bustage
authorTim Taubert <ttaubert@mozilla.com>
Thu, 04 Sep 2014 15:08:57 -0400
changeset 203545 8703c1895505a3593917743af059e06b712105e3
parent 203544 f550c2de2ddc12f05a6a0481a5e03ac4f68ccfac
child 203605 4e7e1ce170ab031653d6ee4c96d57765b2e1276e
child 203783 55e95d529fd3d5791fbfae88dad7a4ad86a92ca8
push idunknown
push userunknown
push dateunknown
reviewersbustage
bugs961867
milestone35.0a1
backs out1917953b1f899ff2f9550476c85ace7e43f0991e
Backed out changeset 1917953b1f89 (bug 961867) on a CLOSED TREE r=bustage
browser/base/content/browser.js
browser/base/content/content.js
browser/base/content/tabbrowser.xml
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_bug575561.js
browser/base/content/test/general/head.js
dom/interfaces/base/nsITabChild.idl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
testing/mochitest/tests/SimpleTest/EventUtils.js
toolkit/modules/BrowserUtils.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3538,52 +3538,21 @@ var XULBrowserWindow = {
       field.label = text;
       field.setAttribute("crop", type == "overLink" ? "center" : "end");
       this.statusText = text;
     }
   },
 
   // Called before links are navigated to to allow us to retarget them if needed.
   onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
-    let target = this._onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
+    let target = BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
     SocialUI.closeSocialPanelForLinkTraversal(target, linkNode);
     return target;
   },
 
-  _onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
-    // Don't modify non-default targets or targets that aren't in top-level app
-    // tab docshells (isAppTab will be false for app tab subframes).
-    if (originalTarget != "" || !isAppTab)
-      return originalTarget;
-
-    // External links from within app tabs should always open in new tabs
-    // instead of replacing the app tab's page (Bug 575561)
-    let linkHost;
-    let docHost;
-    try {
-      linkHost = linkURI.host;
-      docHost = linkNode.ownerDocument.documentURIObject.host;
-    } catch(e) {
-      // nsIURI.host can throw for non-nsStandardURL nsIURIs.
-      // If we fail to get either host, just return originalTarget.
-      return originalTarget;
-    }
-
-    if (docHost == linkHost)
-      return originalTarget;
-
-    // Special case: ignore "www" prefix if it is part of host string
-    let [longHost, shortHost] =
-      linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost];
-    if (longHost == "www." + shortHost)
-      return originalTarget;
-
-    return "_blank";
-  },
-
   onProgressChange: function (aWebProgress, aRequest,
                               aCurSelfProgress, aMaxSelfProgress,
                               aCurTotalProgress, aMaxTotalProgress) {
     // Do nothing.
   },
 
   onProgressChange64: function (aWebProgress, aRequest,
                                 aCurSelfProgress, aMaxSelfProgress,
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -655,8 +655,32 @@ function gKeywordURIFixup(fixupInfo) {
   }
 
   sendAsyncMessage("Browser:URIFixup", data);
 }
 Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup", false);
 addEventListener("unload", () => {
   Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
 }, false);
+
+addMessageListener("Browser:AppTab", function(message) {
+  docShell.isAppTab = message.data.isAppTab;
+});
+
+let WebBrowserChrome = {
+  onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+    return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
+  },
+};
+
+if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+  let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsITabChild);
+  tabchild.webBrowserChrome = WebBrowserChrome;
+}
+
+addEventListener("pageshow", function(event) {
+  if (event.target == content.document) {
+    sendAsyncMessage("PageVisibility:Show", {
+      persisted: event.persisted,
+    });
+  }
+});
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -284,19 +284,17 @@
             this.showTab(aTab);
 
           this.moveTabTo(aTab, this._numPinnedTabs);
           aTab.setAttribute("pinned", "true");
           this.tabContainer._unlockTabSizing();
           this.tabContainer._positionPinnedTabs();
           this.tabContainer.adjustTabstrip();
 
-          // Bug 961867 - [e10s] Implement the logic for app tabs
-          if (!gMultiProcessBrowser)
-            this.getBrowserForTab(aTab).docShell.isAppTab = true;
+          this.getBrowserForTab(aTab).messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: true })
 
           if (aTab.selected)
             this._setCloseKeyState(false);
 
           let event = document.createEvent("Events");
           event.initEvent("TabPinned", true, false);
           aTab.dispatchEvent(event);
         ]]></body>
@@ -310,19 +308,17 @@
 
           this.moveTabTo(aTab, this._numPinnedTabs - 1);
           aTab.removeAttribute("pinned");
           aTab.style.MozMarginStart = "";
           this.tabContainer._unlockTabSizing();
           this.tabContainer._positionPinnedTabs();
           this.tabContainer.adjustTabstrip();
 
-          // Bug 961867 - [e10s] Implement the logic for app tabs
-          if (!gMultiProcessBrowser)
-            this.getBrowserForTab(aTab).docShell.isAppTab = false;
+          this.getBrowserForTab(aTab).messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: false })
 
           if (aTab.selected)
             this._setCloseKeyState(true);
 
           let event = document.createEvent("Events");
           event.initEvent("TabUnpinned", true, false);
           aTab.dispatchEvent(event);
         ]]></body>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -202,49 +202,42 @@ skip-if = e10s # Bug 940195 - XULBrowser
 [browser_bug563588.js]
 [browser_bug565575.js]
 skip-if = e10s
 [browser_bug565667.js]
 run-if = toolkit == "cocoa"
 [browser_bug567306.js]
 skip-if = e10s
 [browser_bug575561.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_bug575830.js]
 skip-if = e10s # Bug 691614 - no e10s zoom support yet
 [browser_bug577121.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_bug578534.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content
 [browser_bug579872.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_bug580638.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_bug580956.js]
 skip-if = e10s # Bug 516755 - SessionStore disabled for e10s
 [browser_bug581242.js]
 skip-if = e10s # Bug 930863 - pageshow issues ("TypeError: charset is undefined" in pageshow listener, as document is null)
 [browser_bug581253.js]
 skip-if = e10s # Bug 930863 - pageshow issues ("TypeError: charset is undefined" in pageshow listener, as document is null)
 [browser_bug581947.js]
 skip-if = e10s
 [browser_bug585558.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_bug585785.js]
 [browser_bug585830.js]
 [browser_bug590206.js]
 [browser_bug592338.js]
 skip-if = e10s # Bug 653065 - Make the lightweight theme web installer ready for e10s
 [browser_bug594131.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_bug595507.js]
 skip-if = e10s # Bug 691601 - no form submit observers
 [browser_bug596687.js]
 [browser_bug597218.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_bug609700.js]
 skip-if = e10s # Bug 516755 - SessionStore disabled for e10s (calls duplicateTabIn, which uses SessionStore)
 [browser_bug623155.js]
 skip-if = e10s # Bug ?????? - URLBar issues (apparently issues with redirection)
 [browser_bug623893.js]
 skip-if = e10s # Bug 916974 - Session history doesn't work in e10s
 [browser_bug624734.js]
 [browser_bug633691.js]
@@ -349,42 +342,39 @@ skip-if = e10s # Bug 921952 - Content:Cl
 skip-if = e10s # Bug ?????? - test directly manipulates content (TypeError: gBrowser.docShell is null)
 [browser_mixedcontent_securityflags.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content ("cannot ipc non-cpow object")
 [browser_notification_tab_switching.js]
 skip-if = buildapp == 'mulet' || e10s # Bug ?????? - uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32
 [browser_offlineQuotaNotification.js]
 skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (gBrowser.selectedBrowser.contentWindow.applicationCache.oncached = function() {...})
 [browser_overflowScroll.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_pageInfo.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 866413 - PageInfo doesn't work in e10s
 [browser_page_style_menu.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content
 
 [browser_parsable_css.js]
 skip-if = e10s
 [browser_parsable_script.js]
 skip-if = debug || asan # Times out on debug/asan, and we are less picky about our JS there
 
 [browser_pinnedTabs.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_plainTextLinks.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (creates and fetches elements directly from content document)
 [browser_popupUI.js]
 skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (tries to get a popup element directly from content)
 [browser_printpreview.js]
 skip-if = buildapp == 'mulet' || e10s # Bug ?????? - timeout after logging "Error: Channel closing: too late to send/recv, messages will be lost"
 [browser_private_browsing_window.js]
 skip-if = buildapp == 'mulet'
 [browser_private_no_prompt.js]
 skip-if = buildapp == 'mulet'
 [browser_relatedTabs.js]
 [browser_removeTabsToTheEnd.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 skip-if = e10s
 [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'
@@ -460,23 +450,20 @@ skip-if = e10s # Bug ????? - test calls 
 skip-if = e10s
 [browser_urlbar_search_healthreport.js]
 skip-if = e10s # Bug ?????? - FHR tests failing (either with "no data for today" or "2 records for today")
 [browser_utilityOverlay.js]
 [browser_visibleFindSelection.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content
 [browser_visibleLabel.js]
 [browser_visibleTabs.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_visibleTabs_bookmarkAllPages.js]
 skip-if = true # Bug 1005420 - fails intermittently. also with e10s enabled: bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined'
 [browser_visibleTabs_bookmarkAllTabs.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_visibleTabs_contextMenu.js]
-skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_visibleTabs_tabPreview.js]
 skip-if = (os == "win" && !debug) || e10s # Bug 1007418 / Bug 698371 - thumbnail captures need e10s love (tabPreviews_capture fails with Argument 1 of CanvasRenderingContext2D.drawWindow does not implement interface Window.)
 [browser_web_channel.js]
 [browser_windowopen_reflows.js]
 skip-if = buildapp == 'mulet'
 [browser_wyciwyg_urlbarCopying.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
 [browser_zbug569342.js]
--- a/browser/base/content/test/general/browser_bug575561.js
+++ b/browser/base/content/test/general/browser_bug575561.js
@@ -38,36 +38,28 @@ function test() {
   });
 }
 
 function testLink(aLinkIndex, pinTab, expectNewTab, nextTest, testSubFrame) {
   let appTab = gBrowser.addTab("http://example.com/browser/browser/base/content/test/general/app_bug575561.html", {skipAnimation: true});
   if (pinTab)
     gBrowser.pinTab(appTab);
   gBrowser.selectedTab = appTab;
-  appTab.linkedBrowser.addEventListener("load", onLoad, true);
 
-  let loadCount = 0;
-  function onLoad() {
-    loadCount++;
-    if (loadCount < 2)
-      return;
-
-    appTab.linkedBrowser.removeEventListener("load", onLoad, true);
-
+  waitForDocLoadComplete(appTab.linkedBrowser).then(function() {
     let browser = gBrowser.getBrowserForTab(appTab);
     if (testSubFrame)
       browser = browser.contentDocument.getElementsByTagName("iframe")[0];
 
     let links = browser.contentDocument.getElementsByTagName("a");
 
     if (expectNewTab)
       gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, true);
     else
-      browser.addEventListener("load", onPageLoad, true);
+      waitForDocLoadComplete(appTab.linkedBrowser).then(onPageLoad);
 
     info("Clicking " + links[aLinkIndex].textContent);
     EventUtils.sendMouseEvent({type:"click"}, links[aLinkIndex], browser.contentWindow);
     let linkLocation = links[aLinkIndex].href;
 
     function onPageLoad() {
       browser.removeEventListener("load", onPageLoad, true);
       is(browser.contentDocument.location.href, linkLocation, "Link should not open in a new tab");
@@ -75,16 +67,18 @@ function testLink(aLinkIndex, pinTab, ex
         gBrowser.removeTab(appTab);
         nextTest();
       });
     }
 
     function onTabOpen(event) {
       gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true);
       ok(true, "Link should open a new tab");
-      executeSoon(function(){
-        gBrowser.removeTab(appTab);
-        gBrowser.removeCurrentTab();
-        nextTest();
+      waitForDocLoadComplete(event.target.linkedBrowser).then(function() {
+        executeSoon(function(){
+          gBrowser.removeTab(appTab);
+          gBrowser.removeCurrentTab();
+          nextTest();
+        });
       });
     }
-  }
+  });
 }
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -427,16 +427,41 @@ function waitForDocLoadAndStopIt(aExpect
 
     let mm = aBrowser.messageManager;
     mm.loadFrameScript("data:,(" + content_script.toString() + ")();", true);
     mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete);
     info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
   });
 }
 
+/**
+ * Waits for the next load to complete in the current browser.
+ *
+ * @return promise
+ */
+function waitForDocLoadComplete(aBrowser=gBrowser) {
+  let deferred = Promise.defer();
+  let progressListener = {
+    onStateChange: function (webProgress, req, flags, status) {
+      let docStart = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
+                     Ci.nsIWebProgressListener.STATE_STOP;
+      if ((flags & docStart) == docStart) {
+        aBrowser.removeProgressListener(progressListener);
+        info("Browser loaded");
+        deferred.resolve();
+      }
+    },
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                           Ci.nsISupportsWeakReference])
+  };
+  aBrowser.addProgressListener(progressListener);
+  info("Waiting for browser load");
+  return deferred.promise;
+}
+
 let FullZoomHelper = {
 
   selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) {
     if (!tab)
       throw new Error("tab must be given.");
     if (gBrowser.selectedTab == tab)
       return Promise.resolve();
     gBrowser.selectedTab = tab;
--- a/dom/interfaces/base/nsITabChild.idl
+++ b/dom/interfaces/base/nsITabChild.idl
@@ -1,16 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #include "domstubs.idl"
 interface nsIContentFrameMessageManager;
+interface nsIWebBrowserChrome3;
 
 [scriptable, uuid(2eb3bc54-78bf-40f2-b301-a5b5b70f7da0)]
 interface nsITabChild : nsISupports
 {
   readonly attribute nsIContentFrameMessageManager messageManager;
 
+  attribute nsIWebBrowserChrome3 webBrowserChrome;
+
   [notxpcom] void sendRequestFocus(in boolean canFocus);
 };
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -75,16 +75,17 @@
 #include "nsViewportInfo.h"
 #include "JavaScriptChild.h"
 #include "nsILoadContext.h"
 #include "ipc/nsGUIEventIPC.h"
 #include "mozilla/gfx/Matrix.h"
 #include "UnitTransforms.h"
 #include "ClientLayerManager.h"
 #include "LayersLogging.h"
+#include "nsIWebBrowserChrome3.h"
 
 #include "nsColorPickerProxy.h"
 
 #define BROWSER_ELEMENT_CHILD_SCRIPT \
     NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js")
 
 #define TABC_LOG(...)
 // #define TABC_LOG(...) printf_stderr("TABC: " __VA_ARGS__)
@@ -200,22 +201,24 @@ TabChildBase::~TabChildBase()
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(TabChildBase)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TabChildBase)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTabChildGlobal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousGlobalScopes)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebBrowserChrome)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TabChildBase)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTabChildGlobal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebBrowserChrome)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(TabChildBase)
   for (uint32_t i = 0; i < tmp->mAnonymousGlobalScopes.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAnonymousGlobalScopes[i])
   }
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
@@ -1309,16 +1312,21 @@ TabChild::FocusPrevElement()
 {
   SendMoveFocus(false);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::GetInterface(const nsIID & aIID, void **aSink)
 {
+    if (aIID.Equals(NS_GET_IID(nsIWebBrowserChrome3))) {
+      NS_IF_ADDREF(((nsISupports *) (*aSink = mWebBrowserChrome)));
+      return NS_OK;
+    }
+
     // XXXbz should we restrict the set of interfaces we hand out here?
     // See bug 537429
     return QueryInterface(aIID, aSink);
 }
 
 NS_IMETHODIMP
 TabChild::ProvideWindow(nsIDOMWindow* aParent, uint32_t aChromeFlags,
                         bool aCalledFromJS,
@@ -2835,16 +2843,30 @@ TabChild::GetMessageManager(nsIContentFr
   if (mTabChildGlobal) {
     NS_ADDREF(*aResult = mTabChildGlobal);
     return NS_OK;
   }
   *aResult = nullptr;
   return NS_ERROR_FAILURE;
 }
 
+NS_IMETHODIMP
+TabChild::GetWebBrowserChrome(nsIWebBrowserChrome3** aWebBrowserChrome)
+{
+  NS_IF_ADDREF(*aWebBrowserChrome = mWebBrowserChrome);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TabChild::SetWebBrowserChrome(nsIWebBrowserChrome3* aWebBrowserChrome)
+{
+  mWebBrowserChrome = aWebBrowserChrome;
+  return NS_OK;
+}
+
 void
 TabChild::SendRequestFocus(bool aCanFocus)
 {
   PBrowserChild::SendRequestFocus(aCanFocus);
 }
 
 PIndexedDBChild*
 TabChild::AllocPIndexedDBChild(
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -28,16 +28,17 @@
 #include "nsITabChild.h"
 #include "nsITooltipListener.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/TabContext.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/layers/CompositorTypes.h"
+#include "nsIWebBrowserChrome3.h"
 
 class nsICachedFileDescriptorListener;
 class nsIDOMWindowUtils;
 
 namespace mozilla {
 namespace layout {
 class RenderFrameChild;
 }
@@ -219,16 +220,17 @@ protected:
 
 protected:
     CSSSize mOldViewportSize;
     bool mContentDocumentIsDisplayed;
     nsRefPtr<TabChildGlobal> mTabChildGlobal;
     ScreenIntSize mInnerSize;
     mozilla::layers::FrameMetrics mLastRootMetrics;
     mozilla::layout::ScrollingBehavior mScrolling;
+    nsCOMPtr<nsIWebBrowserChrome3> mWebBrowserChrome;
 };
 
 class TabChild MOZ_FINAL : public TabChildBase,
                            public PBrowserChild,
                            public nsIWebBrowserChrome2,
                            public nsIEmbeddingSiteWindow,
                            public nsIWebBrowserChromeFocus,
                            public nsIInterfaceRequestor,
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -49,17 +49,17 @@ function sendMouseEvent(aEvent, aTarget,
   if (['click', 'contextmenu', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
     throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'");
   }
 
   if (!aWindow) {
     aWindow = window;
   }
 
-  if (!(aTarget instanceof aWindow.Element)) {
+  if (typeof aTarget == "string") {
     aTarget = aWindow.document.getElementById(aTarget);
   }
 
   var event = aWindow.document.createEvent('MouseEvent');
 
   var typeArg          = aEvent.type;
   var canBubbleArg     = true;
   var cancelableArg    = true;
@@ -969,18 +969,18 @@ function synthesizeText(aEvent, aWindow)
 
   compositionString.dispatchEvent();
 }
 
 // Must be synchronized with nsIDOMWindowUtils.
 const QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK          = 0x0000;
 const QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK              = 0x0001;
 
-const SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK          = 0x0000;
-const SELECTION_SET_FLAG_USE_XP_LINE_BREAK              = 0x0001;
+const SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK          = 0x0000;
+const SELECTION_SET_FLAG_USE_XP_LINE_BREAK              = 0x0001;
 const SELECTION_SET_FLAG_REVERSE                        = 0x0002;
 
 /**
  * Synthesize a query selected text event.
  *
  * @param aWindow  Optional (If null, current |window| will be used)
  * @return         An nsIQueryContentEventResult object.  If this failed,
  *                 the result might be null.
--- a/toolkit/modules/BrowserUtils.jsm
+++ b/toolkit/modules/BrowserUtils.jsm
@@ -143,9 +143,40 @@ this.BrowserUtils = {
     }
     let win = null;
     if (element == aElement)
       win = aTopLevelWindow;
     else
       win = element.contentDocument.defaultView;
     return { targetWindow: win, offsetX: offsetX, offsetY: offsetY };
   },
+
+  onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+    // Don't modify non-default targets or targets that aren't in top-level app
+    // tab docshells (isAppTab will be false for app tab subframes).
+    if (originalTarget != "" || !isAppTab)
+      return originalTarget;
+
+    // External links from within app tabs should always open in new tabs
+    // instead of replacing the app tab's page (Bug 575561)
+    let linkHost;
+    let docHost;
+    try {
+      linkHost = linkURI.host;
+      docHost = linkNode.ownerDocument.documentURIObject.host;
+    } catch(e) {
+      // nsIURI.host can throw for non-nsStandardURL nsIURIs.
+      // If we fail to get either host, just return originalTarget.
+      return originalTarget;
+    }
+
+    if (docHost == linkHost)
+      return originalTarget;
+
+    // Special case: ignore "www" prefix if it is part of host string
+    let [longHost, shortHost] =
+      linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost];
+    if (longHost == "www." + shortHost)
+      return originalTarget;
+
+    return "_blank";
+  },
 };