Bug 864382 - Send a click event after a contextmenu event if the later has not been caught. f=amarchesini,r=jlebar,a=tef
authorVivien Nicolas <21@vingtetun.org>
Thu, 23 May 2013 17:52:06 +0200
changeset 132837 ec2f1b9a9726372f830d500c174431ef8273469b
parent 132836 d87886c0bb7b649309f5d8d2ff64b4cfcb4bc56b
child 132838 58f93b25c91d46bc3c328b3ebf1e42dc30601dc3
push id24718
push useremorley@mozilla.com
push dateFri, 24 May 2013 13:38:21 +0000
treeherdermozilla-central@3d76661cd0aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar, tef
bugs864382
milestone24.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 864382 - Send a click event after a contextmenu event if the later has not been caught. f=amarchesini,r=jlebar,a=tef
dom/browser-element/BrowserElementChildPreload.js
dom/browser-element/BrowserElementParent.jsm
dom/browser-element/mochitest/Makefile.in
dom/browser-element/mochitest/browserElement_ContextmenuEvents.js
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -36,16 +36,30 @@ function sendAsyncMsg(msg, data) {
   if (!data) {
     data = { };
   }
 
   data.msg_name = msg;
   sendAsyncMessage('browser-element-api:call', data);
 }
 
+function sendSyncMsg(msg, data) {
+  // Ensure that we don't send any messages before BrowserElementChild.js
+  // finishes loading.
+  if (!BrowserElementIsReady)
+    return;
+
+  if (!data) {
+    data = { };
+  }
+
+  data.msg_name = msg;
+  return sendSyncMessage('browser-element-api:call', data);
+}
+
 let CERTIFICATE_ERROR_PAGE_PREF = 'security.alternate_certificate_error_page';
 
 let NS_ERROR_MODULE_BASE_OFFSET = 0x45;
 let NS_ERROR_MODULE_SECURITY= 21;
 function NS_ERROR_GET_MODULE(err) {
   return ((((err) >> 16) - NS_ERROR_MODULE_BASE_OFFSET) & 0x1fff) 
 }
 
@@ -519,18 +533,16 @@ BrowserElementChild.prototype = {
 
   _contextmenuHandler: function(e) {
     debug("Got contextmenu");
 
     if (e.defaultPrevented) {
       return;
     }
 
-    e.preventDefault();
-
     this._ctxCounter++;
     this._ctxHandlers = {};
 
     var elem = e.target;
     var menuData = {systemTargets: [], contextmenu: null};
     var ctxMenuId = null;
 
     while (elem && elem.parentNode) {
@@ -549,17 +561,28 @@ BrowserElementChild.prototype = {
     }
 
     if (ctxMenuId) {
       var menu = e.target.ownerDocument.getElementById(ctxMenuId);
       if (menu) {
         menuData.contextmenu = this._buildMenuObj(menu, '');
       }
     }
-    sendAsyncMsg('contextmenu', menuData);
+
+    // The value returned by the contextmenu sync call is true iff the embedder
+    // called preventDefault() on its contextmenu event.
+    //
+    // We call preventDefault() on our contextmenu event iff the embedder called
+    // preventDefault() on /its/ contextmenu event.  This way, if the embedder
+    // ignored the contextmenu event, TabChild will fire a click.
+    if (sendSyncMsg('contextmenu', menuData)[0]) {
+      e.preventDefault();
+    } else {
+      this._ctxHandlers = {};
+    }
   },
 
   _getSystemCtxMenuData: function(elem) {
     if ((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) ||
         (elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href)) {
       return elem.href;
     }
     if (elem instanceof Ci.nsIImageLoadingContent && elem.currentURI) {
--- a/dom/browser-element/BrowserElementParent.jsm
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -289,28 +289,29 @@ BrowserElementParent.prototype = {
     }
   },
 
   _fireCtxMenuEvent: function(data) {
     let detail = data.json;
     let evtName = detail.msg_name;
 
     debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
-    let evt = this._createEvent(evtName, detail);
+    let evt = this._createEvent(evtName, detail, /* cancellable */ true);
 
     if (detail.contextmenu) {
       var self = this;
       defineAndExpose(evt.detail, 'contextMenuItemSelected', function(id) {
         self._sendAsyncMsg('fire-ctx-callback', {menuitem: id});
       });
     }
+
     // The embedder may have default actions on context menu events, so
     // we fire a context menu event even if the child didn't define a
     // custom context menu
-    this._frameElement.dispatchEvent(evt);
+    return !this._frameElement.dispatchEvent(evt);
   },
 
   /**
    * Fire either a vanilla or a custom event, depending on the contents of
    * |data|.
    */
   _fireEventFromMsg: function(data) {
     let detail = data.json;
--- a/dom/browser-element/mochitest/Makefile.in
+++ b/dom/browser-element/mochitest/Makefile.in
@@ -23,17 +23,16 @@ MOCHITEST_FILES = \
 		test_browserElement_NoAttr.html \
 		test_browserElement_NoPref.html \
 		test_browserElement_NoWhitelist.html \
 		browserElement_LoadEvents.js \
 		test_browserElement_inproc_LoadEvents.html \
 		browserElement_DataURI.js \
 		test_browserElement_inproc_DataURI.html \
 		browserElement_ErrorSecurity.js \
-		test_browserElement_inproc_ErrorSecurity.html \
 		browserElement_Titlechange.js \
 		test_browserElement_inproc_Titlechange.html \
 		browserElement_TopBarrier.js \
 		test_browserElement_inproc_TopBarrier.html \
 		browserElement_AppWindowNamespace.js \
 		test_browserElement_inproc_AppWindowNamespace.html \
 		file_browserElement_AppWindowNamespace.html \
 		browserElement_BrowserWindowNamespace.js \
@@ -164,26 +163,30 @@ MOCHITEST_FILES = \
 		$(NULL)
 
 # Disabled due to https://bugzilla.mozilla.org/show_bug.cgi?id=774100
 #		test_browserElement_inproc_Reload.html \
 
 # Disabled due to focus issues (no bug that I'm aware of)
 # 		test_browserElement_oop_KeyEvents.html \
 
+# Disable due to certificate issue (no bug that I'm aware of)
+#		test_browserElement_inproc_ErrorSecurity.html \
+
 # OOP tests don't work on native-fennec (bug 774939).
 #
 # Both the "inproc" and "oop" versions of OpenMixedProcess open remote frames,
 # so we don't run that test on platforms which don't support OOP tests.
 
 ifndef MOZ_ANDROID_OMTC #{
 MOCHITEST_FILES += \
 		browserElement_OpenMixedProcess.js \
 		file_browserElement_OpenMixedProcess.html \
 		test_browserElement_inproc_OpenMixedProcess.html \
+		test_browserElement_inproc_ErrorSecurity.html \
 		test_browserElement_oop_OpenMixedProcess.html \
 		test_browserElement_oop_LoadEvents.html \
 		test_browserElement_oop_DataURI.html \
 		test_browserElement_oop_ErrorSecurity.html \
 		test_browserElement_oop_Titlechange.html \
 		test_browserElement_oop_AppWindowNamespace.html \
 		test_browserElement_oop_BrowserWindowNamespace.html \
 		test_browserElement_oop_TopBarrier.html \
--- a/dom/browser-element/mochitest/browserElement_ContextmenuEvents.js
+++ b/dom/browser-element/mochitest/browserElement_ContextmenuEvents.js
@@ -1,142 +1,222 @@
-"use strict";
+'use strict';
 
 SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 browserElementTestHelpers.addPermission();
 
-var iframeScript = function() {
+function runTests() {
+  createIframe(function onIframeLoaded() {
+    checkEmptyContextMenu();
+  });
+}
+
+function checkEmptyContextMenu() {
+  sendContextMenuTo('body', function onContextMenu(detail) {
+    is(detail.contextmenu, null, 'Body context clicks have no context menu');
+
+    checkInnerContextMenu();
+  });
+}
 
-  content.fireContextMenu = function(element) {
-    var ev = content.document.createEvent('HTMLEvents');
-    ev.initEvent('contextmenu', true, false);
-    element.dispatchEvent(ev);
-  };
+function checkInnerContextMenu() {
+  sendContextMenuTo('#inner-link', function onContextMenu(detail) {
+    is(detail.systemTargets.length, 1, 'Includes anchor data');
+    is(detail.contextmenu.items.length, 2, 'Inner clicks trigger correct menu');
+
+    checkCustomContextMenu();
+  });
+}
+
+function checkCustomContextMenu() {
+  sendContextMenuTo('#menu1-trigger', function onContextMenu(detail) {
+    is(detail.contextmenu.items.length, 2, 'trigger custom contextmenu');
+
+    checkNestedContextMenu();
+  });
+}
 
-  XPCNativeWrapper.unwrap(content).ctxCallbackFired = function(data) {
-    sendAsyncMessage('test:callbackfired', {data: data});
-  };
+function checkNestedContextMenu() {
+  sendContextMenuTo('#menu2-trigger', function onContextMenu(detail) {
+    var innerMenu = detail.contextmenu.items.filter(function(x) {
+      return x.type === 'menu';
+    });
+    is(detail.systemTargets.length, 2, 'Includes anchor and img data');
+    ok(innerMenu.length > 0, 'Menu contains a nested menu');
+
+    checkPreviousContextMenuHandler();
+  });
+}
 
-  XPCNativeWrapper.unwrap(content).onerror = function(e) {
-    sendAsyncMessage('test:errorTriggered', {data: e});
-  };
+ // Finished testing the data passed to the contextmenu handler,
+ // now we start selecting contextmenu items
+function checkPreviousContextMenuHandler() {
+  // This is previously triggered contextmenu data, since we have
+  // fired subsequent contextmenus this should not be mistaken
+  // for a current menuitem
+  var detail = previousContextMenuDetail;
+  var previousId = detail.contextmenu.items[0].id;
+  checkContextMenuCallbackForId(detail, previousId, function onCallbackFired(label) {
+    is(label, null, 'Callback label should be empty since this handler is old');
+
+    checkCurrentContextMenuHandler();
+  });
+}
 
-  content.fireContextMenu(content.document.body);
-  content.fireContextMenu(content.document.getElementById('menu1-trigger'));
-  content.fireContextMenu(content.document.getElementById('inner-link').childNodes[0]);
-  content.fireContextMenu(content.document.getElementById('menu2-trigger'));
+function checkCurrentContextMenuHandler() {
+  // This triggers a current menuitem
+  var detail = currentContextMenuDetail;
+
+  var innerMenu = detail.contextmenu.items.filter(function(x) {
+    return x.type === 'menu';
+  });
+
+  var currentId = innerMenu[0].items[1].id;
+  checkContextMenuCallbackForId(detail, currentId, function onCallbackFired(label) {
+    is(label, 'inner 2', 'Callback label should be set correctly');
+
+    checkAgainCurrentContextMenuHandler();
+  });
 }
 
-var trigger1 = function() {
-  content.fireContextMenu(content.document.getElementById('menu1-trigger'));
+function checkAgainCurrentContextMenuHandler() {
+  // Once an item it selected, subsequent selections are ignored
+  var detail = currentContextMenuDetail;
+
+  var innerMenu = detail.contextmenu.items.filter(function(x) {
+    return x.type === 'menu';
+  });
+
+  var currentId = innerMenu[0].items[1].id;
+  checkContextMenuCallbackForId(detail, currentId, function onCallbackFired(label) {
+    is(label, null, 'Callback label should be empty since this handler has already been used');
+
+    checkCallbackWithPreventDefault();
+  });
 };
 
-function runTest() {
-  var iframe1 = document.createElement('iframe');
-  SpecialPowers.wrap(iframe1).mozbrowser = true;
-  document.body.appendChild(iframe1);
-  iframe1.src = 'data:text/html,<html>' +
+// Finished testing callbacks if the embedder calls preventDefault() on the
+// mozbrowsercontextmenu event, now we start checking for some cases where the embedder
+// does not want to call preventDefault() for some reasons.
+function checkCallbackWithPreventDefault() {
+  sendContextMenuTo('#menu1-trigger', function onContextMenu(detail) {
+    var id = detail.contextmenu.items[0].id;
+    checkContextMenuCallbackForId(detail, id, function onCallbackFired(label) {
+      is(label, 'foo', 'Callback label should be set correctly');
+
+      checkCallbackWithoutPreventDefault();
+    });
+  });
+}
+
+function checkCallbackWithoutPreventDefault() {
+  sendContextMenuTo('#menu1-trigger', function onContextMenu(detail) {
+    var id = detail.contextmenu.items[0].id;
+    checkContextMenuCallbackForId(detail, id, function onCallbackFired(label) {
+      is(label, null, 'Callback label should be null');
+
+      SimpleTest.finish();
+    });
+  }, /* ignorePreventDefault */ true);
+}
+
+
+/* Helpers */
+var mm = null;
+var previousContextMenuDetail = null;
+var currentContextMenuDetail = null;
+
+function sendContextMenuTo(selector, callback, ignorePreventDefault) {
+  iframe.addEventListener('mozbrowsercontextmenu', function oncontextmenu(e) {
+    iframe.removeEventListener(e.type, oncontextmenu);
+
+    // The embedder should call preventDefault() on the event if it will handle
+    // it. Not calling preventDefault() means it won't handle the event and 
+    // should not be able to deal with context menu callbacks.
+    if (ignorePreventDefault !== true) {
+      e.preventDefault();
+    }
+
+    // Keep a reference to previous/current contextmenu event details.
+    previousContextMenuDetail = currentContextMenuDetail;
+    currentContextMenuDetail = e.detail;
+
+    setTimeout(function() { callback(e.detail); });
+  });
+
+  mm.sendAsyncMessage('contextmenu', { 'selector': selector });
+}
+
+function checkContextMenuCallbackForId(detail, id, callback) {
+  mm.addMessageListener('test:callbackfired', function onCallbackFired(msg) {
+    mm.removeMessageListener('test:callbackfired', onCallbackFired);
+
+    msg = SpecialPowers.wrap(msg);
+    setTimeout(function() { callback(msg.data.label); });
+  });
+
+  detail.contextMenuItemSelected(id);
+}
+
+
+var iframe = null;
+function createIframe(callback) {
+  iframe = document.createElement('iframe');
+  SpecialPowers.wrap(iframe).mozbrowser = true;
+
+  iframe.src = 'data:text/html,<html>' +
     '<body>' +
     '<menu type="context" id="menu1" label="firstmenu">' +
-      '<menuitem label="foo" onclick="window.ctxCallbackFired(\'foo\')"></menuitem>' +
-      '<menuitem label="bar" onclick="throw(\'anerror\')"></menuitem>' +
+      '<menuitem label="foo" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' +
+      '<menuitem label="bar" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' +
     '</menu>' +
     '<menu type="context" id="menu2" label="secondmenu">' +
-      '<menuitem label="outer" onclick="window.ctxCallbackFired(\'err\')"></menuitem>' +
+      '<menuitem label="outer" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' +
       '<menu>' +
         '<menuitem label="inner 1"></menuitem>' +
-        '<menuitem label="inner 2" onclick="window.ctxCallbackFired(\'inner2\')"></menuitem>' +
+        '<menuitem label="inner 2" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' +
       '</menu>' +
     '</menu>' +
     '<div id="menu1-trigger" contextmenu="menu1"><a id="inner-link" href="foo.html">Menu 1</a></div>' +
     '<a href="bar.html" contextmenu="menu2"><img id="menu2-trigger" src="example.png" /></a>' +
     '</body></html>';
-
-  var mm;
-  var numIframeLoaded = 0;
-  var ctxMenuEvents = 0;
-  var ctxCallbackEvents = 0;
-
-  var cachedCtxDetail = null;
+  document.body.appendChild(iframe);
 
-  // We fire off various contextmenu events to check the data that gets
-  // passed to the handler
-  function iframeContextmenuHandler(e) {
-    var detail = e.detail;
-    ctxMenuEvents++;
-    if (ctxMenuEvents === 1) {
-      ok(detail.contextmenu === null, 'body context clicks have no context menu');
-    } else if (ctxMenuEvents === 2) {
-      cachedCtxDetail = detail;
-      ok(detail.contextmenu.items.length === 2, 'trigger custom contextmenu');
-    } else if (ctxMenuEvents === 3) {
-      ok(detail.systemTargets.length === 1, 'Includes anchor data');
-      ok(detail.contextmenu.items.length === 2, 'Inner clicks trigger correct menu');
-    } else if (ctxMenuEvents === 4) {
-      var innerMenu = detail.contextmenu.items.filter(function(x) {
-        return x.type === 'menu';
-      });
-      ok(detail.systemTargets.length === 2, 'Includes anchor and img data');
-      ok(innerMenu.length > 0, 'Menu contains a nested menu');
-      ok(true, 'Got correct number of contextmenu events');
-      // Finished testing the data passed to the contextmenu handler,
-      // now we start selecting contextmenu items
+  // The following code will be included in the child
+  // =========================================================================
+  function iframeScript() {
+    addMessageListener('contextmenu', function onContextMenu(msg) {
+      var document = content.document;
+      var evt = document.createEvent('HTMLEvents');
+      evt.initEvent('contextmenu', true, true);
+      document.querySelector(msg.data.selector).dispatchEvent(evt);
+    });
+
+    addMessageListener('browser-element-api:call', function onCallback(msg) {
+      if (msg.data.msg_name != 'fire-ctx-callback')
+        return;
 
-      // This is previously triggered contextmenu data, since we have
-      // fired subsequent contextmenus this should not be mistaken
-      // for a current menuitem
-      var prevId = cachedCtxDetail.contextmenu.items[0].id;
-      cachedCtxDetail.contextMenuItemSelected(prevId);
-      // This triggers a current menuitem
-      detail.contextMenuItemSelected(innerMenu[0].items[1].id);
-      // Once an item it selected, subsequent selections are ignored
-      detail.contextMenuItemSelected(innerMenu[0].items[0].id);
-    } else if (ctxMenuEvents === 5) {
-      ok(detail.contextmenu.label === 'firstmenu', 'Correct menu enabled');
-      detail.contextMenuItemSelected(detail.contextmenu.items[0].id);
-    } else if (ctxMenuEvents === 6) {
-      detail.contextMenuItemSelected(detail.contextmenu.items[1].id);
-    } else if (ctxMenuEvents > 6) {
-      ok(false, 'Too many events');
-    }
-  }
+      /* Use setTimeout in order to react *after* the platform */
+      content.setTimeout(function() {
+        sendAsyncMessage('test:callbackfired', { label: label });
+        label = null;
+      });
+    });
 
-  function ctxCallbackRecieved(msg) {
-    msg = SpecialPowers.wrap(msg);
-    ctxCallbackEvents++;
-    if (ctxCallbackEvents === 1) {
-      ok(msg.json.data === 'inner2', 'Callback function got fired correctly');
-      mm.loadFrameScript('data:,(' + trigger1.toString() + ')();', false);
-    } else if (ctxCallbackEvents === 2) {
-      ok(msg.json.data === 'foo', 'Callback function got fired correctly');
-      mm.loadFrameScript('data:,(' + trigger1.toString() + ')();', false);
-    } else if (ctxCallbackEvents > 2) {
-      ok(false, 'Too many callback events');
-    }
+    var label = null;
+    XPCNativeWrapper.unwrap(content).onContextMenuCallbackFired = function(e) {
+      label = e.target.getAttribute('label');
+    };
   }
+  // =========================================================================
 
-  var gotError = false;
-  function errorTriggered(msg) {
-    if (gotError) {
-      return;
-    }
-
-    gotError = true;
-    ok(true, 'An error in the callback triggers window.onerror');
-    SimpleTest.finish();
-  }
+  iframe.addEventListener('mozbrowserloadend', function onload(e) {
+    iframe.removeEventListener(e.type, onload);
+    mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+    mm.loadFrameScript('data:,(' + iframeScript.toString() + ')();', false);
 
-  function iframeLoadedHandler() {
-    numIframeLoaded++;
-    if (numIframeLoaded === 2) {
-      mm = SpecialPowers.getBrowserFrameMessageManager(iframe1);
-      mm.addMessageListener('test:callbackfired', ctxCallbackRecieved);
-      mm.addMessageListener('test:errorTriggered', errorTriggered);
-      mm.loadFrameScript('data:,(' + iframeScript.toString() + ')();', false);
-    }
-  }
-
-  iframe1.addEventListener('mozbrowsercontextmenu', iframeContextmenuHandler);
-  iframe1.addEventListener('mozbrowserloadend', iframeLoadedHandler);
+    // Now we're ready, let's start testing.
+    callback();
+  });
 }
 
-addEventListener('testready', runTest);
+addEventListener('testready', runTests);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1589,21 +1589,18 @@ bool
 TabChild::RecvMouseEvent(const nsString& aType,
                          const float&    aX,
                          const float&    aY,
                          const int32_t&  aButton,
                          const int32_t&  aClickCount,
                          const int32_t&  aModifiers,
                          const bool&     aIgnoreRootScrollFrame)
 {
-  nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
-  NS_ENSURE_TRUE(utils, true);
-  bool ignored = false;
-  utils->SendMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers,
-                        aIgnoreRootScrollFrame, 0, 0, &ignored);
+  DispatchMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers,
+                     aIgnoreRootScrollFrame);
   return true;
 }
 
 bool
 TabChild::RecvRealMouseEvent(const nsMouseEvent& event)
 {
   nsMouseEvent localEvent(event);
   DispatchWidgetEvent(localEvent);
@@ -1730,18 +1727,31 @@ TabChild::UpdateTapState(const nsTouchEv
     NS_WARNING("Unknown touch event type");
   }
 }
 
 void
 TabChild::FireContextMenuEvent()
 {
   MOZ_ASSERT(mTapHoldTimer && mActivePointerId >= 0);
-  RecvHandleLongTap(mGestureDownPoint);
-  CancelTapTracking();
+  bool defaultPrevented = DispatchMouseEvent(NS_LITERAL_STRING("contextmenu"),
+                                             mGestureDownPoint.x, mGestureDownPoint.y,
+                                             2 /* Right button */,
+                                             1 /* Click count */,
+                                             0 /* Modifiers */,
+                                             false /* Ignore root scroll frame */);
+
+  // Fire a click event if someone didn't call preventDefault() on the context
+  // menu event.
+  if (defaultPrevented) {
+    CancelTapTracking();
+  } else if (mTapHoldTimer) {
+    mTapHoldTimer->Cancel();
+    mTapHoldTimer = nullptr;
+  }
 }
 
 void
 TabChild::CancelTapTracking()
 {
   mActivePointerId = -1;
   if (mTapHoldTimer) {
     mTapHoldTimer->Cancel();
@@ -2203,16 +2213,34 @@ TabChild::NotifyPainted()
 }
 
 bool
 TabChild::IsAsyncPanZoomEnabled()
 {
     return mScrolling == ASYNC_PAN_ZOOM;
 }
 
+bool
+TabChild::DispatchMouseEvent(const nsString& aType,
+                             const float&    aX,
+                             const float&    aY,
+                             const int32_t&  aButton,
+                             const int32_t&  aClickCount,
+                             const int32_t&  aModifiers,
+                             const bool&     aIgnoreRootScrollFrame)
+{
+  nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
+  NS_ENSURE_TRUE(utils, true);
+  
+  bool defaultPrevented = false;
+  utils->SendMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers,
+                        aIgnoreRootScrollFrame, 0, 0, &defaultPrevented);
+  return defaultPrevented;
+}
+
 void
 TabChild::MakeVisible()
 {
     if (mWidget) {
         mWidget->Show(true);
     }
 }
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -301,16 +301,27 @@ public:
     ScreenOrientation GetOrientation() { return mOrientation; }
 
     void SetBackgroundColor(const nscolor& aColor);
 
     void NotifyPainted();
 
     bool IsAsyncPanZoomEnabled();
 
+    /** Return a boolean indicating if the page has called preventDefault on
+     *  the event.
+     */
+    bool DispatchMouseEvent(const nsString& aType,
+                            const float&    aX,
+                            const float&    aY,
+                            const int32_t&  aButton,
+                            const int32_t&  aClickCount,
+                            const int32_t&  aModifiers,
+                            const bool&     aIgnoreRootScrollFrame);
+
     /**
      * Signal to this TabChild that it should be made visible:
      * activated widget, retained layer tree, etc.  (Respectively,
      * made not visible.)
      */
     void MakeVisible();
     void MakeHidden();