Bug 897062 - Handle special clicks in e10s. r=felipe,smaug
authorTom Schuster <evilpies@gmail.com>
Thu, 08 Aug 2013 20:16:47 -0400
changeset 141847 57b34c38a191731de4497fde68b7630d9487190e
parent 141846 adf7ff5f11d3155e0d8e2709c1616fa5069dae79
child 141848 cdce12c4f6994d6928bef92ee53d05780221dca5
push id32248
push userevilpies@gmail.com
push dateFri, 09 Aug 2013 00:18:11 +0000
treeherdermozilla-inbound@57b34c38a191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe, smaug
bugs897062
milestone26.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 897062 - Handle special clicks in e10s. r=felipe,smaug
browser/base/content/browser.js
browser/base/content/content.js
browser/components/nsBrowserGlue.js
browser/modules/ContentClick.jsm
browser/modules/moz.build
dom/ipc/TabChild.h
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -727,19 +727,22 @@ const gFormSubmitObserver = {
 };
 
 var gBrowserInit = {
   onLoad: function() {
     gMultiProcessBrowser = gPrefService.getBoolPref("browser.tabs.remote");
 
     var mustLoadSidebar = false;
 
-    Cc["@mozilla.org/eventlistenerservice;1"]
-      .getService(Ci.nsIEventListenerService)
-      .addSystemEventListener(gBrowser, "click", contentAreaClick, true);
+    if (!gMultiProcessBrowser) {
+      // There is a Content:Click message manually sent from content.
+      Cc["@mozilla.org/eventlistenerservice;1"]
+        .getService(Ci.nsIEventListenerService)
+        .addSystemEventListener(gBrowser, "click", contentAreaClick, true);
+    }
 
     gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
 
     // Note that the XBL binding is untrusted
     gBrowser.addEventListener("PluginBindingAttached", gPluginHandler, true, true);
     gBrowser.addEventListener("PluginCrashed",         gPluginHandler, true);
     gBrowser.addEventListener("PluginOutdated",        gPluginHandler, true);
     gBrowser.addEventListener("PluginInstantiated",    gPluginHandler, true);
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -190,8 +190,102 @@ let AboutHomeListener = {
         aIID.equals(Ci.nsISupports)) {
       return this;
     }
 
     throw Components.results.NS_ERROR_NO_INTERFACE;
   }
 };
 AboutHomeListener.init();
+
+var global = this;
+
+let ClickEventHandler = {
+  init: function init() {
+    Cc["@mozilla.org/eventlistenerservice;1"]
+      .getService(Ci.nsIEventListenerService)
+      .addSystemEventListener(global, "click", this, true);
+  },
+
+  handleEvent: function(event) {
+    // Bug 903016: Most of this code is an unfortunate duplication from
+    // contentAreaClick in browser.js.
+    if (!event.isTrusted || event.defaultPrevented || event.button == 2)
+      return;
+
+    let [href, node] = this._hrefAndLinkNodeForClickEvent(event);
+
+    let json = { button: event.button, shiftKey: event.shiftKey,
+                 ctrlKey: event.ctrlKey, metaKey: event.metaKey,
+                 altKey: event.altKey, href: null, title: null,
+                 bookmark: false };
+
+    if (href) {
+      json.href = href;
+      if (node) {
+        json.title = node.getAttribute("title");
+
+        if (event.button == 0 && !event.ctrlKey && !event.shiftKey &&
+            !event.altKey && !event.metaKey) {
+          json.bookmark = node.getAttribute("rel") == "sidebar";
+          if (json.bookmark)
+            event.preventDefault(); // Need to prevent the pageload.
+        }
+      }
+
+      sendAsyncMessage("Content:Click", json);
+      return;
+    }
+
+    // This might be middle mouse navigation.
+    if (event.button == 1)
+      sendAsyncMessage("Content:Click", json);
+  },
+
+  /**
+   * Extracts linkNode and href for the current click target.
+   *
+   * @param event
+   *        The click event.
+   * @return [href, linkNode].
+   *
+   * @note linkNode will be null if the click wasn't on an anchor
+   *       element (or XLink).
+   */
+  _hrefAndLinkNodeForClickEvent: function(event) {
+    function isHTMLLink(aNode) {
+      // Be consistent with what nsContextMenu.js does.
+      return ((aNode instanceof content.HTMLAnchorElement && aNode.href) ||
+              (aNode instanceof content.HTMLAreaElement && aNode.href) ||
+              aNode instanceof content.HTMLLinkElement);
+    }
+
+    function makeURLAbsolute(aBase, aUrl) {
+      // Note:  makeURI() will throw if aUri is not a valid URI
+      return makeURI(aUrl, null, makeURI(aBase)).spec;
+    }
+
+    let node = event.target;
+    while (node && !isHTMLLink(node)) {
+      node = node.parentNode;
+    }
+
+    if (node)
+      return [node.href, node];
+
+    // If there is no linkNode, try simple XLink.
+    let href, baseURI;
+    node = event.target;
+    while (node && !href) {
+      if (node.nodeType == content.Node.ELEMENT_NODE) {
+        href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
+        if (href)
+          baseURI = node.baseURI;
+      }
+      node = node.parentNode;
+    }
+
+    // In case of XLink, we don't return the node we got href from since
+    // callers expect <a>-like elements.
+    return [href ? makeURLAbsolute(baseURI, href) : null, null];
+  }
+};
+ClickEventHandler.init();
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -15,16 +15,19 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource:///modules/SignInToWebsite.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
                                   "resource:///modules/AboutHome.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "ContentClick",
+                                  "resource:///modules/ContentClick.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
@@ -462,16 +465,19 @@ BrowserGlue.prototype = {
     PageThumbs.init();
     NewTabUtils.init();
     BrowserNewTabPreloader.init();
     SignInToWebsiteUX.init();
     PdfJs.init();
     webrtcUI.init();
     AboutHome.init();
 
+    if (Services.prefs.getBoolPref("browser.tabs.remote"))
+      ContentClick.init();
+
     Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
   },
 
   _checkForOldBuildUpdates: function () {
     // check for update if our build is old
     if (Services.prefs.getBoolPref("app.update.enabled") &&
         Services.prefs.getBoolPref("app.update.checkInstallTime")) {
 
new file mode 100644
--- /dev/null
+++ b/browser/modules/ContentClick.jsm
@@ -0,0 +1,82 @@
+/* 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/. */
+
+"use strict";
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "ContentClick" ];
+
+Cu.import("resource:///modules/PlacesUIUtils.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+let ContentClick = {
+  init: function() {
+    let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+    mm.addMessageListener("Content:Click", this);
+  },
+
+  receiveMessage: function (message) {
+    switch (message.name) {
+      case "Content:Click":
+        this.contentAreaClick(message.json, message.target)
+        break;
+    }
+  },
+
+  contentAreaClick: function (json, browser) {
+    // This is heavily based on contentAreaClick from browser.js (Bug 903016)
+    // The json is set up in a way to look like an Event.
+    let window = browser.ownerDocument.defaultView;
+
+    if (!json.href) {
+      // Might be middle mouse navigation.
+      if (Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
+          !Services.prefs.getBoolPref("general.autoScroll")) {
+        window.middleMousePaste(json);
+      }
+      return;
+    }
+
+    if (json.bookmark) {
+      // This is the Opera convention for a special link that, when clicked,
+      // allows to add a sidebar panel.  The link's title attribute contains
+      // the title that should be used for the sidebar panel.
+      PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                       , type: "bookmark"
+                                       , uri: Services.io.newURI(json.href, null, null)
+                                       , title: json.title
+                                       , loadBookmarkInSidebar: true
+                                       , hiddenRows: [ "description"
+                                                     , "location"
+                                                     , "keyword" ]
+                                       }, window);
+      return;
+    }
+
+    // Note: We don't need the sidebar code here.
+
+    // This part is based on handleLinkClick.
+    var where = window.whereToOpenLink(json);
+    if (where == "current")
+      return false;
+
+    // Todo(903022): code for where == save
+
+    window.openLinkIn(json.href, where, { referrerURI: browser.documentURI,
+                                          charset: browser.characterSet });
+
+    // Mark the page as a user followed link.  This is done so that history can
+    // distinguish automatic embed visits from user activated ones.  For example
+    // pages loaded in frames are embed visits and lost with the session, while
+    // visits across frames should be preserved.
+    try {
+      if (!PrivateBrowsingUtils.isWindowPrivate(window))
+        PlacesUIUtils.markPageAsFollowedLink(href);
+    } catch (ex) { /* Skip invalid URIs. */ }
+  }
+};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -3,16 +3,17 @@
 # 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/.
 
 TEST_DIRS += ['test']
 
 EXTRA_JS_MODULES += [
     'BrowserNewTabPreloader.jsm',
+    'ContentClick.jsm',
     'NetworkPrioritizer.jsm',
     'SharedFrame.jsm',
     'SignInToWebsite.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'offlineAppCache.jsm',
     'openLocationLastURL.jsm',
     'webappsUI.jsm',
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -11,16 +11,17 @@
 #include "mozilla/dom/PBrowserChild.h"
 #endif
 #ifdef DEBUG
 #include "PCOMContentPermissionRequestChild.h"
 #endif /* DEBUG */
 #include "nsIWebNavigation.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
+#include "nsEventDispatcher.h"
 #include "nsIWebBrowserChrome2.h"
 #include "nsIEmbeddingSiteWindow.h"
 #include "nsIWebBrowserChromeFocus.h"
 #include "nsIWidget.h"
 #include "nsIDOMEventListener.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIWindowProvider.h"
 #include "jsapi.h"
@@ -114,16 +115,23 @@ public:
                               uint8_t optional_argc) MOZ_OVERRIDE
   {
     return nsDOMEventTargetHelper::AddEventListener(aType, aListener,
                                                     aUseCapture,
                                                     aWantsUntrusted,
                                                     optional_argc);
   }
 
+  nsresult
+  PreHandleEvent(nsEventChainPreVisitor& aVisitor)
+  {
+    aVisitor.mForceContentDispatch = true;
+    return NS_OK;
+  }
+
   virtual JSContext* GetJSContextForEventHandlers() MOZ_OVERRIDE;
   virtual nsIPrincipal* GetPrincipal() MOZ_OVERRIDE;
 
   nsCOMPtr<nsIContentFrameMessageManager> mMessageManager;
   TabChild* mTabChild;
 };
 
 class ContentListener MOZ_FINAL : public nsIDOMEventListener