Bug 575561 - External links from within app tabs should always open in new tabs instead of replacing the app tab's page. r=gavin+bz, a=blocking-beta7
☠☠ backed out by a55262a6b891 ☠ ☠
authorMargaret Leibovic <margaret.leibovic@gmail.com>
Thu, 28 Oct 2010 15:00:31 -0700
changeset 56661 ecd1948992eba338e5ea7928dadf64862bb277d7
parent 56660 46df8a54499019c3d63650bb9fed0b66b2d6749e
child 56662 069ccd107c321c27cbf4d52473de1ec085c8714f
child 56677 a55262a6b891ff3b62add041b27cedae8ccd1614
push idunknown
push userunknown
push dateunknown
reviewersgavin, blocking-beta7
bugs575561
milestone2.0b8pre
Bug 575561 - External links from within app tabs should always open in new tabs instead of replacing the app tab's page. r=gavin+bz, a=blocking-beta7
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
embedding/browser/webBrowser/Makefile.in
embedding/browser/webBrowser/nsIWebBrowserChrome3.idl
toolkit/components/help/content/help.js
toolkit/content/Services.jsm
toolkit/content/tests/chrome/findbar_window.xul
xpfe/appshell/public/nsIXULBrowserWindow.idl
xpfe/appshell/src/nsContentTreeOwner.cpp
xpfe/appshell/src/nsContentTreeOwner.h
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3980,16 +3980,37 @@ var XULBrowserWindow = {
 
   setOverLink: function (link) {
     // Encode bidirectional formatting characters.
     // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
     link = link.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
                         encodeURIComponent);
     gURLBar.setOverLink(link);
   },
+  
+  // Called before links are navigated to to allow us to retarget them if needed.
+  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;
+
+    let docURI = linkNode.ownerDocument.documentURIObject;
+    try {
+      let docURIDomain = Services.eTLD.getBaseDomain(docURI, 0);
+      let linkURIDomain = Services.eTLD.getBaseDomain(linkURI, 0);
+      // External links from within app tabs should always open in new tabs
+      // instead of replacing the app tab's page (Bug 575561)
+      if (docURIDomain != linkURIDomain)
+        return "_blank";
+    } catch(e) {
+      // If getBaseDomain fails, we return originalTarget below.
+    }
+    return originalTarget;
+  },
 
   onLinkIconAvailable: function (aIconURL) {
     if (gProxyFavIcon && gBrowser.userTypedValue === null)
       PageProxySetIcon(aIconURL); // update the favicon in the URL bar
   },
 
   onProgressChange: function (aWebProgress, aRequest,
                               aCurSelfProgress, aMaxSelfProgress,
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -186,16 +186,18 @@
           if (aTab.hidden)
             this.showTab(aTab);
 
           this.moveTabTo(aTab, this._numPinnedTabs);
           aTab.setAttribute("pinned", "true");
           this.tabContainer._positionPinnedTabs();
           this.tabContainer.adjustTabstrip();
 
+          this.getBrowserForTab(aTab).docShell.isAppTab = true;
+
           let event = document.createEvent("Events");
           event.initEvent("TabPinned", true, false);
           aTab.dispatchEvent(event);
         ]]></body>
       </method>
 
       <method name="unpinTab">
         <parameter name="aTab"/>
@@ -205,16 +207,18 @@
 
           this.moveTabTo(aTab, this._numPinnedTabs - 1);
           aTab.setAttribute("fadein", "true");
           aTab.removeAttribute("pinned");
           aTab.style.MozMarginStart = "";
           this.tabContainer._positionPinnedTabs();
           this.tabContainer.adjustTabstrip();
 
+          this.getBrowserForTab(aTab).docShell.isAppTab = false;
+
           let event = document.createEvent("Events");
           event.initEvent("TabUnpinned", true, false);
           aTab.dispatchEvent(event);
         ]]></body>
       </method>
 
       <method name="previewTab">
         <parameter name="aTab"/>
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -154,17 +154,17 @@
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheChannel.h"
 #include "nsIApplicationCacheContainer.h"
 #include "nsIPermissionManager.h"
 #include "nsStreamUtils.h"
 #include "nsIController.h"
 #include "nsPICommandUpdater.h"
 #include "nsIDOMHTMLAnchorElement.h"
-#include "nsIWebBrowserChrome2.h"
+#include "nsIWebBrowserChrome3.h"
 #include "nsITabChild.h"
 #include "nsIStrictTransportSecurityService.h"
 
 // Editor-related
 #include "nsIEditingSession.h"
 
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
@@ -706,16 +706,17 @@ nsDocShell::nsDocShell():
     mAllowDNSPrefetch(PR_TRUE),
     mCreatingDocument(PR_FALSE),
     mUseErrorPages(PR_FALSE),
     mObserveErrorPages(PR_TRUE),
     mAllowAuth(PR_TRUE),
     mAllowKeywordFixup(PR_FALSE),
     mIsOffScreenBrowser(PR_FALSE),
     mIsActive(PR_TRUE),
+    mIsAppTab(PR_FALSE),
     mFiredUnloadEvent(PR_FALSE),
     mEODForCurrentDocument(PR_FALSE),
     mURIResultedInDocument(PR_FALSE),
     mIsBeingDestroyed(PR_FALSE),
     mIsExecutingOnLoadHandler(PR_FALSE),
     mIsPrintingOrPP(PR_FALSE),
     mSavingOldViewer(PR_FALSE)
 #ifdef DEBUG
@@ -4798,16 +4799,30 @@ nsDocShell::SetIsActive(PRBool aIsActive
 NS_IMETHODIMP
 nsDocShell::GetIsActive(PRBool *aIsActive)
 {
   *aIsActive = mIsActive;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDocShell::SetIsAppTab(PRBool aIsAppTab)
+{
+  mIsAppTab = aIsAppTab;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetIsAppTab(PRBool *aIsAppTab)
+{
+  *aIsAppTab = mIsAppTab;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocShell::SetVisibility(PRBool aVisibility)
 {
     if (!mContentViewer)
         return NS_OK;
     if (aVisibility) {
         mContentViewer->Show();
     }
     else {
@@ -11306,18 +11321,32 @@ nsDocShell::OnLinkClick(nsIContent* aCon
   if (!IsOKToLoadURI(aURI)) {
     return NS_OK;
   }
 
   if (aContent->IsEditable()) {
     return NS_OK;
   }
 
+  nsresult rv = NS_ERROR_FAILURE;
+  nsAutoString target;
+
+  nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
+  if (browserChrome3) {
+    nsCOMPtr<nsIDOMNode> linkNode = do_QueryInterface(aContent);
+    nsAutoString oldTarget(aTargetSpec);
+    rv = browserChrome3->OnBeforeLinkTraversal(oldTarget, aURI,
+                                               linkNode, mIsAppTab, target);
+  }
+  
+  if (NS_FAILED(rv))
+    target = aTargetSpec;  
+
   nsCOMPtr<nsIRunnable> ev =
-      new OnLinkClickEvent(this, aContent, aURI, aTargetSpec,
+      new OnLinkClickEvent(this, aContent, aURI, target.get(),
                            aPostDataStream, aHeadersDataStream);
   return NS_DispatchToCurrentThread(ev);
 }
 
 NS_IMETHODIMP
 nsDocShell::OnLinkClickSync(nsIContent *aContent,
                             nsIURI* aURI,
                             const PRUnichar* aTargetSpec,
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -785,16 +785,17 @@ protected:
     PRPackedBool               mAllowDNSPrefetch;
     PRPackedBool               mCreatingDocument; // (should be) debugging only
     PRPackedBool               mUseErrorPages;
     PRPackedBool               mObserveErrorPages;
     PRPackedBool               mAllowAuth;
     PRPackedBool               mAllowKeywordFixup;
     PRPackedBool               mIsOffScreenBrowser;
     PRPackedBool               mIsActive;
+    PRPackedBool               mIsAppTab;
 
     // This boolean is set to true right before we fire pagehide and generally
     // unset when we embed a new content viewer.  While it's true no navigation
     // is allowed in this docshell.
     PRPackedBool               mFiredUnloadEvent;
 
     // this flag is for bug #21358. a docshell may load many urls
     // which don't result in new documents being created (i.e. a new
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -66,17 +66,17 @@ interface nsIRequest;
 interface nsISHEntry;
 interface nsILayoutHistoryState;
 interface nsISecureBrowserUI;
 interface nsIDOMStorage;
 interface nsIPrincipal;
 interface nsIWebBrowserPrint;
 interface nsIVariant;
 
-[scriptable, uuid(74470127-87eb-4f79-8293-1616fe9cb689)]
+[scriptable, uuid(98cdbcc4-2d81-4191-a63f-b6c52085edbc)]
 interface nsIDocShell : nsISupports
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -524,21 +524,27 @@ interface nsIDocShell : nsISupports
 
   /**
    * Sets whether a docshell is active. An active docshell is one that is
    * visible, and thus is not a good candidate for certain optimizations
    * like image frame discarding. Docshells are active unless told otherwise.
    */
   attribute boolean isActive;
 
-
   /**
    * The ID of the docshell in the session history.
    */
   readonly attribute unsigned long long historyID;
+
+  /**
+   * Sets whether a docshell is an app tab. An app tab docshell may behave
+   * differently than a non-app tab docshell in some cases, such as when
+   * handling link clicks. Docshells are not app tabs unless told otherwise.
+   */
+  attribute boolean isAppTab;
 };
 
 [uuid(5f7a2184-31b6-4d67-9c75-0c17477766e2)]
 interface nsIDocShell_MOZILLA_2_0_BRANCH : nsISupports {
   /**
    * Create a new about:blank document and content viewer.
    * @param aPrincipal the principal to use for the new document.
    */
--- a/embedding/browser/webBrowser/Makefile.in
+++ b/embedding/browser/webBrowser/Makefile.in
@@ -73,16 +73,17 @@ SDK_XPIDLSRCS   = \
                 $(NULL)
 
 XPIDLSRCS	= \
 		nsCWebBrowser.idl			\
 		nsICommandHandler.idl			\
 		nsIEmbeddingSiteWindow2.idl \
 		nsIContextMenuListener2.idl \
 		nsIWebBrowserChrome2.idl	    \
+		nsIWebBrowserChrome3.idl	    \
 		$(NULL)
 
 CPPSRCS		= \
 		nsDocShellTreeOwner.cpp		\
 		nsWebBrowser.cpp		\
 		nsCommandHandler.cpp		\
         nsWebBrowserContentPolicy.cpp   \
 		nsContextMenuInfo.cpp		\
new file mode 100644
--- /dev/null
+++ b/embedding/browser/webBrowser/nsIWebBrowserChrome3.idl
@@ -0,0 +1,65 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Margaret Leibovic <margaret.leibovic@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#include "nsIWebBrowserChrome2.idl"
+#include "nsIURI.idl"
+#include "nsIDOMNode.idl"
+
+/**
+ * nsIWebBrowserChrome3 is an extension to nsIWebBrowserChrome2.
+ */
+[scriptable, uuid(7f2aa813-b250-4e46-afeb-97b1e91bc9a5)]
+interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2
+{
+  /**
+   * Determines the appropriate target for a link.
+   *
+   * @param originalTarget
+   *        The original link target.
+   * @param linkURI
+   *        Link destination URI.
+   * @param aDOMNode
+   *        Link DOM node.
+   * @param isAppTab
+   *        Whether or not the link is in an app tab.
+   * @returns A new link target, if appropriate.
+   *          Otherwise returns originalTarget.
+   */
+  AString onBeforeLinkTraversal(in AString originalTarget,
+                                in nsIURI linkURI,
+                                in nsIDOMNode linkNode,
+                                in PRBool isAppTab);
+};
--- a/toolkit/components/help/content/help.js
+++ b/toolkit/components/help/content/help.js
@@ -557,17 +557,18 @@ nsHelpStatusHandler.prototype = {
     },
 
     init : function() {},
 
     destroy : function() {},
 
     setJSStatus : function(status) {},
     setJSDefaultStatus : function(status) {},
-    setOverLink : function(link, context) {}
+    setOverLink : function(link, context) {},
+    onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {}
 }
 
 function UpdateBackForwardButtons() {
     var backBroadcaster = document.getElementById("canGoBack");
     var forwardBroadcaster = document.getElementById("canGoForward");
     var webNavigation = getWebNavigation();
 
     // Avoid setting attributes on broadcasters if the value hasn't changed!
--- a/toolkit/content/Services.jsm
+++ b/toolkit/content/Services.jsm
@@ -125,8 +125,12 @@ XPCOMUtils.defineLazyServiceGetter(Servi
 
 XPCOMUtils.defineLazyServiceGetter(Services, "strings",
                                    "@mozilla.org/intl/stringbundle;1",
                                    "nsIStringBundleService");
 
 XPCOMUtils.defineLazyServiceGetter(Services, "urlFormatter",
                                    "@mozilla.org/toolkit/URLFormatterService;1",
                                    "nsIURLFormatter");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "eTLD",
+                                   "@mozilla.org/network/effective-tld-service;1",
+                                   "nsIEffectiveTLDService");
--- a/toolkit/content/tests/chrome/findbar_window.xul
+++ b/toolkit/content/tests/chrome/findbar_window.xul
@@ -71,17 +71,19 @@
         throw Cr.NS_NOINTERFACE;
       },
 
       setJSStatus: function() { },
       setJSDefaultStatus: function() { },
 
       setOverLink: function(aStatusText, aLink) {
         gStatusText = aStatusText;
-      }
+      },
+
+      onBeforeLinkTraversal: function() { }
     };
 
     function ok(condition, message) {
       window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
     }
     function finish() {
       window.close();
       window.opener.wrappedJSObject.SimpleTest.finish();
--- a/xpfe/appshell/public/nsIXULBrowserWindow.idl
+++ b/xpfe/appshell/public/nsIXULBrowserWindow.idl
@@ -33,16 +33,18 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
+#include "nsIURI.idl"
+#include "nsIDOMNode.idl"
 
 interface nsIRequest;
 interface nsIDOMElement;
 
 /**
  * The nsIXULBrowserWindow supplies the methods that may be called from the
  * internals of the browser area to tell the containing xul window to update
  * its ui. 
@@ -60,10 +62,18 @@ interface nsIXULBrowserWindow : nsISuppo
    */
   void setJSDefaultStatus(in AString status);
 
   /**
    * Tells the object implementing this function what link we are currently
    * over.
    */
   void setOverLink(in AString link, in nsIDOMElement element);
+
+  /**
+   * Determines the appropriate target for a link.
+   */
+  AString onBeforeLinkTraversal(in AString originalTarget,
+                                in nsIURI linkURI,
+                                in nsIDOMNode linkNode,
+                                in PRBool isAppTab);
 };
 
--- a/xpfe/appshell/src/nsContentTreeOwner.cpp
+++ b/xpfe/appshell/src/nsContentTreeOwner.cpp
@@ -119,16 +119,17 @@ NS_IMPL_ADDREF(nsContentTreeOwner)
 NS_IMPL_RELEASE(nsContentTreeOwner)
 
 NS_INTERFACE_MAP_BEGIN(nsContentTreeOwner)
    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocShellTreeOwner)
    NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeOwner)
    NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
    NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome)
    NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome2)
+   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome3)
    NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
    NS_INTERFACE_MAP_ENTRY(nsIWindowProvider)
    // NOTE: This is using aggregation because there are some properties and
    // method on nsIBaseWindow (which we implement) and on
    // nsIEmbeddingSiteWindow (which we also implement) that have the same name.
    // And it just so happens that we want different behavior for these methods
    // and properties depending on the interface through which they're called
    // (SetFocus() is a good example here).  If it were not for that, we could
@@ -426,16 +427,39 @@ nsContentTreeOwner::GetPersistence(PRBoo
     *aPersistSize = persistString.Find("width") >= 0 || persistString.Find("height") >= 0 ? PR_TRUE : PR_FALSE;
   if (aPersistSizeMode)
     *aPersistSizeMode = persistString.Find("sizemode") >= 0 ? PR_TRUE : PR_FALSE;
 
   return NS_OK;
 }
 
 //*****************************************************************************
+// nsContentTreeOwner::nsIWebBrowserChrome3
+//*****************************************************************************   
+
+NS_IMETHODIMP nsContentTreeOwner::OnBeforeLinkTraversal(const nsAString &originalTarget,
+                                                        nsIURI *linkURI,
+                                                        nsIDOMNode *linkNode,
+                                                        PRBool isAppTab,
+                                                        nsAString &_retval)
+{
+  NS_ENSURE_STATE(mXULWindow);
+
+  nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow;
+  mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow));
+
+  if (xulBrowserWindow)
+    return xulBrowserWindow->OnBeforeLinkTraversal(originalTarget, linkURI,
+                                                   linkNode, isAppTab, _retval);
+  
+  _retval = originalTarget;
+  return NS_OK;
+}
+
+//*****************************************************************************
 // nsContentTreeOwner::nsIWebBrowserChrome2
 //*****************************************************************************   
 
 NS_IMETHODIMP nsContentTreeOwner::SetStatusWithContext(PRUint32 aStatusType,
                                                        const nsAString &aStatusText,
                                                        nsISupports *aStatusContext)
 {
   // We only allow the status to be set from the primary content shell
--- a/xpfe/appshell/src/nsContentTreeOwner.h
+++ b/xpfe/appshell/src/nsContentTreeOwner.h
@@ -44,39 +44,40 @@
 #include "nsCOMPtr.h"
 #include "nsString.h"
 
 // Interfaces Needed
 #include "nsIBaseWindow.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
-#include "nsIWebBrowserChrome2.h"
+#include "nsIWebBrowserChrome3.h"
 #include "nsIWindowProvider.h"
 
 class nsXULWindow;
 class nsSiteWindow2;
 
 class nsContentTreeOwner : public nsIDocShellTreeOwner,
                            public nsIBaseWindow,
                            public nsIInterfaceRequestor,
-                           public nsIWebBrowserChrome2,
+                           public nsIWebBrowserChrome3,
                            public nsIWindowProvider
 {
 friend class nsXULWindow;
 friend class nsSiteWindow2;
 
 public:
    NS_DECL_ISUPPORTS
 
    NS_DECL_NSIBASEWINDOW
    NS_DECL_NSIDOCSHELLTREEOWNER
    NS_DECL_NSIINTERFACEREQUESTOR
    NS_DECL_NSIWEBBROWSERCHROME
    NS_DECL_NSIWEBBROWSERCHROME2
+   NS_DECL_NSIWEBBROWSERCHROME3
    NS_DECL_NSIWINDOWPROVIDER
 
 protected:
    nsContentTreeOwner(PRBool fPrimary);
    virtual ~nsContentTreeOwner();
 
    void XULWindow(nsXULWindow* aXULWindow);
    nsXULWindow* XULWindow();