Bug 1531155 - Support context menus in html:textarea in the parent process, r=NeilDeakin
authorAlexander Surkov <surkov.alexander@gmail.com>
Mon, 04 Mar 2019 21:07:28 +0000
changeset 520170 71e5f2f8ca11283011a40cc7a30199417a0e48bf
parent 520169 469b9a04697237a28a1a76e364803c9645b1e1ba
child 520171 818af1db79a228cc7709fd82eabe417e521259d1
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersNeilDeakin
bugs1531155
milestone67.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 1531155 - Support context menus in html:textarea in the parent process, r=NeilDeakin Differential Revision: https://phabricator.services.mozilla.com/D21092
toolkit/content/editMenuOverlay.js
toolkit/content/tests/chrome/chrome.ini
toolkit/content/tests/chrome/file_edit_contextmenu.xul
toolkit/content/tests/chrome/test_edit_contextmenu.html
--- a/toolkit/content/editMenuOverlay.js
+++ b/toolkit/content/editMenuOverlay.js
@@ -1,22 +1,22 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 
 /* 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/. */
 
 // update menu items that rely on focus or on the current selection
-function goUpdateGlobalEditMenuItems() {
+function goUpdateGlobalEditMenuItems(force) {
   // Don't bother updating the edit commands if they aren't visible in any way
   // (i.e. the Edit menu isn't open, nor is the context menu open, nor have the
   // cut, copy, and paste buttons been added to the toolbars) for performance.
   // This only works in applications/on platforms that set the gEditUIVisible
   // flag, so we check to see if that flag is defined before using it.
-  if (typeof gEditUIVisible != "undefined" && !gEditUIVisible)
+  if (!force && (typeof gEditUIVisible != "undefined" && !gEditUIVisible))
     return;
 
   goUpdateCommand("cmd_undo");
   goUpdateCommand("cmd_redo");
   goUpdateCommand("cmd_cut");
   goUpdateCommand("cmd_copy");
   goUpdateCommand("cmd_paste");
   goUpdateCommand("cmd_selectAll");
@@ -29,8 +29,43 @@ function goUpdateUndoEditMenuItems() {
   goUpdateCommand("cmd_undo");
   goUpdateCommand("cmd_redo");
 }
 
 // update menu items that depend on clipboard contents
 function goUpdatePasteMenuItems() {
   goUpdateCommand("cmd_paste");
 }
+
+// Support context menus on html textareas in the parent process:
+window.addEventListener("contextmenu", (e) => {
+  // Note that there's not a risk of e.target being XBL anonymous content for <textbox> (which manages
+  // its own context menu), because e.target will be the XBL binding parent in that case.
+  let needsContextMenu = e.target.ownerDocument == document &&
+                         !e.defaultPrevented &&
+                         e.target.localName == "textarea" &&
+                         e.target.namespaceURI == "http://www.w3.org/1999/xhtml";
+
+  if (!needsContextMenu) {
+    return;
+  }
+
+  let popup = document.getElementById("textbox-contextmenu");
+  if (!popup) {
+    MozXULElement.insertFTLIfNeeded("toolkit/main-window/editmenu.ftl");
+    document.documentElement.appendChild(MozXULElement.parseXULToFragment(`
+      <menupopup id="textbox-contextmenu" class="textbox-contextmenu">
+        <menuitem data-l10n-id="editmenu-undo" command="cmd_undo"></menuitem>
+        <menuseparator></menuseparator>
+        <menuitem data-l10n-id="editmenu-cut" command="cmd_cut"></menuitem>
+        <menuitem data-l10n-id="editmenu-copy" command="cmd_copy"></menuitem>
+        <menuitem data-l10n-id="editmenu-paste" command="cmd_paste"></menuitem>
+        <menuitem data-l10n-id="editmenu-delete" command="cmd_delete"></menuitem>
+        <menuseparator></menuseparator>
+        <menuitem data-l10n-id="editmenu-select-all" command="cmd_selectAll"></menuitem>
+      </menupopup>
+    `));
+    popup = document.documentElement.lastElementChild;
+  }
+
+  goUpdateGlobalEditMenuItems(true);
+  popup.openPopupAtScreen(e.screenX, e.screenY, true);
+});
--- a/toolkit/content/tests/chrome/chrome.ini
+++ b/toolkit/content/tests/chrome/chrome.ini
@@ -10,16 +10,17 @@ support-files =
   bug360437_window.xul
   bug366992_window.xul
   bug409624_window.xul
   bug429723_window.xul
   bug624329_window.xul
   dialog_dialogfocus.xul
   dialog_dialogfocus2.xul
   file_empty.xhtml
+  file_edit_contextmenu.xul
   file_about_networking_wsh.py
   file_autocomplete_with_composition.js
   file_editor_with_autocomplete.js
   findbar_entireword_window.xul
   findbar_events_window.xul
   findbar_window.xul
   frame_popup_anchor.xul
   frame_popupremoving_frame.xul
@@ -103,16 +104,17 @@ skip-if = (os == 'mac' && os_version == 
 skip-if = toolkit == "cocoa"
 [test_button.xul]
 [test_closemenu_attribute.xul]
 [test_contextmenu_list.xul]
 [test_custom_element_base.xul]
 [test_custom_element_delay_connection.xul]
 [test_deck.xul]
 [test_dialogfocus.xul]
+[test_edit_contextmenu.html]
 [test_editor_for_input_with_autocomplete.html]
 [test_editor_for_textbox_with_autocomplete.xul]
 [test_findbar.xul]
 subsuite = clipboard
 [test_findbar_entireword.xul]
 [test_findbar_events.xul]
 [test_focus_anons.xul]
 [test_frames.xul]
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/chrome/file_edit_contextmenu.xul
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+       xmlns:html="http://www.w3.org/1999/xhtml">
+
+<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+<!-- Copied from toolkit/content/editMenuCommands.inc.xul -->
+<script type="application/javascript" src="chrome://global/content/editMenuOverlay.js"/>
+<commandset id="editMenuCommands">
+  <commandset id="editMenuCommandSetAll" commandupdater="true" events="focus,select"
+              oncommandupdate="goUpdateGlobalEditMenuItems()"/>
+  <commandset id="editMenuCommandSetUndo" commandupdater="true" events="undo"
+              oncommandupdate="goUpdateUndoEditMenuItems()"/>
+  <commandset id="editMenuCommandSetPaste" commandupdater="true" events="clipboard"
+              oncommandupdate="goUpdatePasteMenuItems()"/>
+  <command id="cmd_undo" oncommand="goDoCommand('cmd_undo')"/>
+  <command id="cmd_redo" oncommand="goDoCommand('cmd_redo')"/>
+  <command id="cmd_cut" oncommand="goDoCommand('cmd_cut')"/>
+  <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')"/>
+  <command id="cmd_paste" oncommand="goDoCommand('cmd_paste')"/>
+  <command id="cmd_delete" oncommand="goDoCommand('cmd_delete')"/>
+  <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/>
+  <command id="cmd_switchTextDirection" oncommand="goDoCommand('cmd_switchTextDirection');"/>
+</commandset>
+
+<html:textarea />
+
+</window>
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/chrome/test_edit_contextmenu.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1513343
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1513343</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+    const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+    SimpleTest.waitForExplicitFinish();
+
+    async function runTest() {
+      let win = window.open("file_edit_contextmenu.xul", "context-menu", "chrome,width=600,height=600");
+      await new Promise(r => win.addEventListener("load", r, { once: true}));
+      await SimpleTest.promiseFocus(win);
+
+      let textarea = win.document.querySelector("textarea");
+      ok(textarea, "textarea exists");
+
+      info("Synthesizing a key so 'Undo' will be enabled");
+      textarea.focus();
+      synthesizeKey("x", {}, win);
+      is(textarea.value, "x", "initial value");
+
+      win.document.addEventListener("contextmenu", (e) => {
+        info("Calling prevent default on the first contextmenu event");
+        e.preventDefault();
+      }, { once: true });
+      synthesizeMouseAtCenter(textarea, {type: "contextmenu"}, win);
+      ok(!win.document.getElementById("textbox-contextmenu"), "contextmenu with preventDefault() doesn't run");
+
+      let popupshown = new Promise(r => win.addEventListener("popupshown", r, { once: true }));
+      synthesizeMouseAtCenter(textarea, {type: "contextmenu"}, win);
+      let contextmenu = win.document.getElementById("textbox-contextmenu");
+      ok(contextmenu, "context menu exists after right click");
+      await popupshown;
+
+      ok(!contextmenu.querySelector("[command=cmd_undo]").hasAttribute("disabled"), "undo enabled");
+      ok(contextmenu.querySelector("[command=cmd_cut]").hasAttribute("disabled"), "cut disabled");
+      ok(contextmenu.querySelector("[command=cmd_copy]").hasAttribute("disabled"), "copy disabled");
+      ok(!contextmenu.querySelector("[command=cmd_paste]").hasAttribute("disabled"), "paste enabled");
+      ok(contextmenu.querySelector("[command=cmd_delete]").hasAttribute("disabled"), "delete disabled");
+      ok(!contextmenu.querySelector("[command=cmd_selectAll]").hasAttribute("disabled"), "select all enabled");
+
+      contextmenu.querySelector("[command=cmd_undo]").click();
+      is(textarea.value, "", "undo worked");
+
+      SimpleTest.finish();
+    }
+
+  </script>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1513343">Mozilla Bug 1513343</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>