Bug 961867: Pass app tab status to child processes and allow onBeforeLinkTraversal to work there. r=smaug, r=felipe
authorDave Townsend <dtownsend@oxymoronical.com>
Tue, 02 Sep 2014 17:43:08 -0400
changeset 203386 52b4f9600a14a5466d0374a187ba947fe061658d
parent 203196 2dd9b2f2ef8fa99d631a4897027e0cf33f0e15e4
child 203387 ee2ad2a64c1eeb79fe3286208a926941ade3d014
push id48665
push userryanvm@gmail.com
push dateWed, 03 Sep 2014 20:40:15 +0000
treeherdermozilla-inbound@0da762e6868a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, felipe
bugs961867
milestone35.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 961867: Pass app tab status to child processes and allow onBeforeLinkTraversal to work there. r=smaug, r=felipe
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
@@ -3556,52 +3556,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
@@ -628,8 +628,24 @@ let DOMFullscreenHandler = {
       this._fullscreenDoc = aEvent.target;
       sendAsyncMessage("MozEnteredDomFullscreen", {
         origin: this._fullscreenDoc.nodePrincipal.origin,
       });
     }
   }
 };
 DOMFullscreenHandler.init();
+
+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;
+}
--- 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
@@ -193,48 +193,41 @@ skip-if = e10s # Bug 691601 - no form su
 [browser_bug562649.js]
 skip-if = e10s # Bug 940195 - XULBrowserWindow.isBusy is false as a remote tab starts loading
 [browser_bug563588.js]
 [browser_bug565575.js]
 [browser_bug565667.js]
 run-if = toolkit == "cocoa"
 [browser_bug567306.js]
 [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]
 [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]
@@ -331,41 +324,38 @@ 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]
 [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]
 [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]
@@ -433,23 +423,20 @@ skip-if = e10s # Bug ????? - test calls 
 [browser_urlbarTrimURLs.js]
 [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
@@ -412,16 +412,41 @@ function waitForDocLoadAndStopIt(aExpect
       }
     },
   };
   aBrowser.addProgressListener(progressListener);
   info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
   return deferred.promise;
 }
 
+/**
+ * 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";
+  },
 };