Bug 1260102 - Pass isHandlingUserInput through process boundary for content menu command. r=mrbkap draft
authorXidorn Quan <quanxunzhen@gmail.com>
Tue, 29 Mar 2016 14:58:43 +1100
changeset 345623 3b687e45d1f732b9e076f7a1d596562a56d332a2
parent 345622 e16b24bd712b567abf1db8b567513683a8700a9d
child 517229 9c60bcd0a1e2f8c5042c5d130ac57b473a19051f
push id14129
push userxquan@mozilla.com
push dateTue, 29 Mar 2016 23:36:28 +0000
reviewersmrbkap
bugs1260102
milestone48.0a1
Bug 1260102 - Pass isHandlingUserInput through process boundary for content menu command. r=mrbkap MozReview-Commit-ID: FMQOFpeO6yn
browser/base/content/content.js
browser/base/content/tab-content.js
browser/modules/E10SUtils.jsm
dom/html/test/browser.ini
dom/html/test/browser_content_contextmenu_userinput.js
dom/html/test/file_content_contextmenu.html
toolkit/modules/PageMenu.jsm
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -11,16 +11,18 @@ var {classes: Cc, interfaces: Ci, utils:
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/ContentWebRTC.jsm");
 Cu.import("resource:///modules/ContentObservers.jsm");
 Cu.import("resource://gre/modules/InlineSpellChecker.jsm");
 Cu.import("resource://gre/modules/InlineSpellCheckerContent.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
+  "resource:///modules/E10SUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
   "resource:///modules/ContentLinkHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
   "resource://gre/modules/LoginManagerContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
   "resource://gre/modules/InsecurePasswordUtils.jsm");
@@ -44,17 +46,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 // TabChildGlobal
 var global = this;
 
 // Load the form validation popup handler
 var formSubmitObserver = new FormSubmitObserver(content, this);
 
 addMessageListener("ContextMenu:DoCustomCommand", function(message) {
-  PageMenuChild.executeMenu(message.data);
+  E10SUtils.wrapHandlingUserInput(
+    content, message.data.handlingUserInput,
+    () => PageMenuChild.executeMenu(message.data.generatedItemId));
 });
 
 addMessageListener("RemoteLogins:fillForm", function(message) {
   LoginManagerContent.receiveMessage(message, content);
 });
 addEventListener("DOMFormHasPassword", function(event) {
   LoginManagerContent.onDOMFormHasPassword(event, content);
   InsecurePasswordUtils.checkForInsecurePasswords(event.target);
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -63,25 +63,20 @@ addMessageListener("Browser:Reload", fun
   try {
     let sh = webNav.sessionHistory;
     if (sh)
       webNav = sh.QueryInterface(Ci.nsIWebNavigation);
   } catch (e) {
   }
 
   let reloadFlags = message.data.flags;
-  let handlingUserInput;
   try {
-    handlingUserInput = content.QueryInterface(Ci.nsIInterfaceRequestor)
-                               .getInterface(Ci.nsIDOMWindowUtils)
-                               .setHandlingUserInput(message.data.handlingUserInput);
-    webNav.reload(reloadFlags);
+    E10SUtils.wrapHandlingUserInput(content, message.data.handlingUserInput,
+                                    () => webNav.reload(reloadFlags));
   } catch (e) {
-  } finally {
-    handlingUserInput.destruct();
   }
 });
 
 addMessageListener("MixedContent:ReenableProtection", function() {
   docShell.mixedContentChannel = null;
 });
 
 addMessageListener("SecondScreen:tab-mirror", function(message) {
--- a/browser/modules/E10SUtils.jsm
+++ b/browser/modules/E10SUtils.jsm
@@ -97,9 +97,21 @@ this.E10SUtils = {
         uri: aURI.spec,
         flags: Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
         referrer: aReferrer ? aReferrer.spec : null,
       },
       historyIndex: sessionHistory.requestedIndex,
     });
     return false;
   },
+
+  wrapHandlingUserInput: function(aWindow, aIsHandling, aCallback) {
+    var handlingUserInput;
+    try {
+      handlingUserInput = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                 .getInterface(Ci.nsIDOMWindowUtils)
+                                 .setHandlingUserInput(aIsHandling);
+      aCallback();
+    } finally {
+      handlingUserInput.destruct();
+    }
+  },
 };
--- a/dom/html/test/browser.ini
+++ b/dom/html/test/browser.ini
@@ -1,23 +1,25 @@
 [DEFAULT]
 support-files =
   bug592641_img.jpg
   dummy_page.html
   file_bug649778.html
   file_bug649778.html^headers^
   file_fullscreen-api-keys.html
+  file_content_contextmenu.html
   head.js
 
 [browser_bug592641.js]
 [browser_bug649778.js]
 skip-if = e10s # Bug ?????? - leaked until shutdown [nsGlobalWindow #16 about:blank]
 [browser_bug1081537.js]
 [browser_bug1108547.js]
 support-files =
   file_bug1108547-1.html
   file_bug1108547-2.html
   file_bug1108547-3.html
 [browser_DOMDocElementInserted.js]
 [browser_fullscreen-api-keys.js]
 tags = fullscreen
 [browser_fullscreen-contextmenu-esc.js]
 tags = fullscreen
+[browser_content_contextmenu_userinput.js]
new file mode 100644
--- /dev/null
+++ b/dom/html/test/browser_content_contextmenu_userinput.js
@@ -0,0 +1,61 @@
+"use strict";
+
+function frameScript() {
+  let Ci = Components.interfaces;
+  let windowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+  let menuitem = content.document.getElementById("menuitem");
+  menuitem.addEventListener("click", function() {
+    sendAsyncMessage("Test:ContextMenuClick", windowUtils.isHandlingUserInput);
+  });
+}
+
+var gMessageManager;
+
+function listenOneMessage(aMsg, aListener) {
+  function listener({ data }) {
+    gMessageManager.removeMessageListener(aMsg, listener);
+    aListener(data);
+  }
+  gMessageManager.addMessageListener(aMsg, listener);
+}
+
+function promiseOneMessage(aMsg) {
+  return new Promise(resolve => listenOneMessage(aMsg, resolve));
+}
+
+const kPage = "http://example.org/browser/" +
+              "dom/html/test/file_content_contextmenu.html";
+
+add_task(function* () {
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: kPage
+  }, function*(aBrowser) {
+    gMessageManager = aBrowser.messageManager;
+    ContentTask.spawn(aBrowser, null, frameScript);
+
+    let contextMenu = document.getElementById("contentAreaContextMenu");
+    ok(contextMenu, "Got context menu");
+
+    info("Open context menu");
+    is(contextMenu.state, "closed", "Should not have opened context menu");
+    let popupShownPromise = promiseWaitForEvent(window, "popupshown");
+    EventUtils.synthesizeMouse(aBrowser, window.innerWidth / 3,
+                               window.innerHeight / 3,
+                               {type: "contextmenu", button: 2}, window);
+    yield popupShownPromise;
+    is(contextMenu.state, "open", "Should have opened context menu");
+
+    let pageMenuSep = document.getElementById("page-menu-separator");
+    ok(pageMenuSep && !pageMenuSep.hidden,
+       "Page menu separator should be shown");
+    let testMenuItem = pageMenuSep.previousSibling;
+    is(testMenuItem.label, "Test Context Menu Click", "Got context menu item");
+
+    let promiseContextMenuClick = promiseOneMessage("Test:ContextMenuClick");
+    EventUtils.synthesizeMouseAtCenter(testMenuItem, {}, window);
+    let isUserInput = yield promiseContextMenuClick;
+    ok(isUserInput, "Content menu click should be a user input");
+  });
+});
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_content_contextmenu.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title></title>
+  <style>
+    body {
+      margin: 0;
+      width: 100vw;
+      height: 100vh;
+    }
+  </style>
+</head>
+<body contextmenu="testmenu">
+  <menu type="context" id="testmenu">
+    <menuitem label="Test Context Menu Click" id="menuitem">
+  </menu>
+</body>
+</html>
--- a/toolkit/modules/PageMenu.jsm
+++ b/toolkit/modules/PageMenu.jsm
@@ -1,14 +1,16 @@
 /* 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/. */
 
 this.EXPORTED_SYMBOLS = ["PageMenuParent", "PageMenuChild"];
 
+var {interfaces: Ci} = Components;
+
 this.PageMenu = function PageMenu() {
 }
 
 PageMenu.prototype = {
   PAGEMENU_ATTR: "pagemenu",
   GENERATEDITEMID_ATTR: "generateditemid",
 
   _popup: null,
@@ -157,18 +159,23 @@ PageMenu.prototype = {
     if (type == "command" && target.hasAttribute(this.GENERATEDITEMID_ATTR)) {
       // If a builder is assigned, call click on it directly. Otherwise, this is
       // likely a menu with data from another process, so send a message to the
       // browser to execute the menuitem.
       if (this._builder) {
         this._builder.click(target.getAttribute(this.GENERATEDITEMID_ATTR));
       }
       else if (this._browser) {
-        this._browser.messageManager.sendAsyncMessage("ContextMenu:DoCustomCommand",
-          target.getAttribute(this.GENERATEDITEMID_ATTR));
+        let win = target.ownerDocument.defaultView;
+        let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsIDOMWindowUtils);
+        this._browser.messageManager.sendAsyncMessage("ContextMenu:DoCustomCommand", {
+          generatedItemId: target.getAttribute(this.GENERATEDITEMID_ATTR),
+          handlingUserInput: windowUtils.isHandlingUserInput
+        });
       }
     } else if (type == "popuphidden" && this._popup == target) {
       this.removeGeneratedContent(this._popup);
 
       this._popup.removeEventListener("popuphidden", this);
       this._popup.removeEventListener("command", this);
 
       this._popup = null;