Bug 1170531 - Disable clipboard menu commands correctly in non-(X)HTML documents; r=ehsan
authorMichael Layzell <michael@thelayzells.com>
Tue, 30 Jun 2015 08:17:27 -0400
changeset 250700 92437417748518bc41e4378258b535b08f142e6f
parent 250699 031e7a57d95a6af4157aba3a8c830dae484df002
child 250701 ee5a1022935699be089928201c42f24349cb80af
push idunknown
push userunknown
push dateunknown
reviewersehsan
bugs1170531
milestone42.0a1
Bug 1170531 - Disable clipboard menu commands correctly in non-(X)HTML documents; r=ehsan
browser/components/customizableui/test/browser_947914_button_copy.js
browser/components/customizableui/test/browser_947914_button_cut.js
browser/devtools/styleinspector/test/browser_computedview_search-filter_context-menu.js
browser/devtools/styleinspector/test/browser_ruleview_search-filter_context-menu.js
dom/base/nsGlobalWindowCommands.cpp
editor/libeditor/nsPlaintextEditor.cpp
editor/libeditor/tests/test_bug1067255.html
toolkit/content/editMenuOverlay.js
toolkit/content/globalOverlay.js
toolkit/content/tests/browser/browser.ini
toolkit/content/tests/browser/browser_bug1170531.js
--- a/browser/components/customizableui/test/browser_947914_button_copy.js
+++ b/browser/components/customizableui/test/browser_947914_button_copy.js
@@ -14,17 +14,17 @@ add_task(function() {
 
   gURLBar.focus();
   info("The URL bar was focused");
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let copyButton = document.getElementById("copy-button");
   ok(copyButton, "Copy button exists in Panel Menu");
-  ok(!copyButton.getAttribute("disabled"), "Copy button is initially enabled");
+  ok(copyButton.getAttribute("disabled"), "Copy button is initially disabled");
 
   // copy text from URL bar
   gURLBar.value = testText;
   gURLBar.focus();
   gURLBar.select();
   yield PanelUI.show();
   info("Menu panel was opened");
 
--- a/browser/components/customizableui/test/browser_947914_button_cut.js
+++ b/browser/components/customizableui/test/browser_947914_button_cut.js
@@ -13,17 +13,17 @@ add_task(function() {
   let testText = "cut text test";
 
   gURLBar.focus();
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let cutButton = document.getElementById("cut-button");
   ok(cutButton, "Cut button exists in Panel Menu");
-  ok(!cutButton.hasAttribute("disabled"), "Cut button is enabled");
+  ok(cutButton.hasAttribute("disabled"), "Cut button is disabled");
 
   // cut text from URL bar
   gURLBar.value = testText;
   gURLBar.focus();
   gURLBar.select();
   yield PanelUI.show();
   info("Menu panel was opened");
 
--- a/browser/devtools/styleinspector/test/browser_computedview_search-filter_context-menu.js
+++ b/browser/devtools/styleinspector/test/browser_computedview_search-filter_context-menu.js
@@ -34,18 +34,18 @@ add_task(function*() {
   let onContextMenuPopup = once(searchContextMenu, "popupshowing");
   EventUtils.synthesizeMouse(searchField, 2, 2,
     {type: "contextmenu", button: 2}, win);
   yield onContextMenuPopup;
 
   is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
   is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
   is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
-  is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
-  is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
+  is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
+  is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
   is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
 
   info("Closing context menu");
   let onContextMenuHidden = once(searchContextMenu, "popuphidden");
   searchContextMenu.hidePopup();
   yield onContextMenuHidden;
 
   info("Copy text in search field using the context menu");
--- a/browser/devtools/styleinspector/test/browser_ruleview_search-filter_context-menu.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_search-filter_context-menu.js
@@ -34,18 +34,18 @@ add_task(function*() {
   let onContextMenuPopup = once(searchContextMenu, "popupshowing");
   EventUtils.synthesizeMouse(searchField, 2, 2,
     {type: "contextmenu", button: 2}, win);
   yield onContextMenuPopup;
 
   is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
   is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
   is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
-  is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
-  is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
+  is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
+  is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
   is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
 
   info("Closing context menu");
   let onContextMenuHidden = once(searchContextMenu, "popuphidden");
   searchContextMenu.hidePopup();
   yield onContextMenuHidden;
 
   info("Copy text in search field using the context menu");
--- a/dom/base/nsGlobalWindowCommands.cpp
+++ b/dom/base/nsGlobalWindowCommands.cpp
@@ -481,24 +481,33 @@ NS_IMPL_ISUPPORTS(nsClipboardCommand, ns
 
 nsresult
 nsClipboardCommand::IsCommandEnabled(const char* aCommandName, nsISupports *aContext, bool *outCmdEnabled)
 {
   NS_ENSURE_ARG_POINTER(outCmdEnabled);
   *outCmdEnabled = false;
 
   if (strcmp(aCommandName, "cmd_copy") &&
-      strcmp(aCommandName, "cmd_copyAndCollapseToEnd"))
+      strcmp(aCommandName, "cmd_copyAndCollapseToEnd") &&
+      strcmp(aCommandName, "cmd_cut"))
     return NS_OK;
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aContext);
   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
-  *outCmdEnabled = nsCopySupport::CanCopy(doc);
+  if (doc->IsHTMLOrXHTML()) {
+    // In HTML and XHTML documents, we always want cut and copy commands to be enabled.
+    *outCmdEnabled = true;
+  } else {
+    // Cut isn't enabled in xul documents which use nsClipboardCommand
+    if (strcmp(aCommandName, "cmd_cut")) {
+      *outCmdEnabled = nsCopySupport::CanCopy(doc);
+    }
+  }
   return NS_OK;
 }
 
 nsresult
 nsClipboardCommand::DoCommand(const char *aCommandName, nsISupports *aContext)
 {
   if (strcmp(aCommandName, "cmd_cut") &&
       strcmp(aCommandName, "cmd_copy") &&
--- a/editor/libeditor/nsPlaintextEditor.cpp
+++ b/editor/libeditor/nsPlaintextEditor.cpp
@@ -1196,32 +1196,38 @@ NS_IMETHODIMP nsPlaintextEditor::Cut()
     DeleteSelection(eNone, eStrip);
   }
   return actionTaken ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP nsPlaintextEditor::CanCut(bool *aCanCut)
 {
   NS_ENSURE_ARG_POINTER(aCanCut);
-  *aCanCut = IsModifiable() && CanCutOrCopy(ePasswordFieldNotAllowed);
+  // Cut is always enabled in HTML documents
+  nsCOMPtr<nsIDocument> doc = GetDocument();
+  *aCanCut = (doc && doc->IsHTMLOrXHTML()) ||
+    (IsModifiable() && CanCutOrCopy(ePasswordFieldNotAllowed));
   return NS_OK;
 }
 
 NS_IMETHODIMP nsPlaintextEditor::Copy()
 {
   bool actionTaken = false;
   FireClipboardEvent(NS_COPY, nsIClipboard::kGlobalClipboard, &actionTaken);
 
   return actionTaken ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP nsPlaintextEditor::CanCopy(bool *aCanCopy)
 {
   NS_ENSURE_ARG_POINTER(aCanCopy);
-  *aCanCopy = CanCutOrCopy(ePasswordFieldNotAllowed);
+  // Copy is always enabled in HTML documents
+  nsCOMPtr<nsIDocument> doc = GetDocument();
+  *aCanCopy = (doc && doc->IsHTMLOrXHTML()) ||
+    CanCutOrCopy(ePasswordFieldNotAllowed);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsPlaintextEditor::CanDelete(bool *aCanDelete)
 {
   NS_ENSURE_ARG_POINTER(aCanDelete);
   *aCanDelete = IsModifiable() && CanCutOrCopy(ePasswordFieldAllowed);
   return NS_OK;
--- a/editor/libeditor/tests/test_bug1067255.html
+++ b/editor/libeditor/tests/test_bug1067255.html
@@ -34,19 +34,22 @@ https://bugzilla.mozilla.org/show_bug.cg
 
         ok(editor1.canCopy(), "can copy, text");
         ok(editor1.canCut(), "can cut, text");
         ok(editor1.canDelete(), "can delete, text");
 
         password.focus();
         password.select();
 
-        ok(!editor2.canCopy(), "can copy, password");
-        ok(!editor2.canCut(), "can cut, password");
-        ok(editor1.canDelete(), "can delete, password");
+        // Copy and cut commands don't do anything on passoword fields by default,
+        // but webpages can hook up event handlers to the event, and thus, we have to
+        // always keep the cut and copy event enabled in HTML/XHTML documents.
+        ok(editor2.canCopy(), "can copy, password");
+        ok(editor2.canCut(), "can cut, password");
+        ok(editor2.canDelete(), "can delete, password");
 
         SimpleTest.finish();
       }
    </script>
   </pre>
 
   <input type="text" value="Gonzo says hi" id="text-field" />
   <input type="password" value="Jan also" id="password-field" />
--- a/toolkit/content/editMenuOverlay.js
+++ b/toolkit/content/editMenuOverlay.js
@@ -12,17 +12,18 @@ function goUpdateGlobalEditMenuItems()
   // 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)
     return;
 
   goUpdateCommand("cmd_undo");
   goUpdateCommand("cmd_redo");
-  // don't update the cmd_cut or cmd_copy items - as we want them to always be enabled
+  goUpdateCommand("cmd_cut");
+  goUpdateCommand("cmd_copy");
   goUpdateCommand("cmd_paste");
   goUpdateCommand("cmd_selectAll");
   goUpdateCommand("cmd_delete");
   goUpdateCommand("cmd_switchTextDirection");
 }
 
 // update menu items that relate to undo/redo
 function goUpdateUndoEditMenuItems()
--- a/toolkit/content/globalOverlay.js
+++ b/toolkit/content/globalOverlay.js
@@ -88,17 +88,17 @@ function goUpdateCommand(aCommand)
   }
 }
 
 function goDoCommand(aCommand)
 {
   try {
     var controller = top.document.commandDispatcher
                         .getControllerForCommand(aCommand);
-    if (controller)
+    if (controller && controller.isCommandEnabled(aCommand))
       controller.doCommand(aCommand);
   }
   catch (e) {
     Components.utils.reportError("An error occurred executing the " +
                                  aCommand + " command: " + e);
   }
 }
 
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -31,8 +31,9 @@ support-files =
   data/post_form_inner.sjs
   data/post_form_outer.sjs
 skip-if = e10s # Bug ?????? - test directly manipulates content (gBrowser.contentDocument.getElementById("postForm").submit();)
 [browser_content_url_annotation.js]
 skip-if = !e10s || !crashreporter
 support-files =
   file_redirect.html
   file_redirect_to.html
+[browser_bug1170531.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_bug1170531.js
@@ -0,0 +1,89 @@
+// Test for bug 1170531
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1170531
+
+add_task(function* () {
+  // Get a bunch of DOM nodes
+  let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).
+        getInterface(Ci.nsIDOMWindowUtils);
+
+  let editMenu = document.getElementById("edit-menu");
+  let menubar = editMenu.parentNode;
+  let menuPopup = editMenu.menupopup;
+  let editMenuIndex = -1;
+  for (let i = 0; i < menubar.children.length; i++) {
+    if (menubar.children[i] === editMenu) {
+      editMenuIndex = i;
+      break;
+    }
+  }
+
+  let closeMenu = function(aCallback) {
+    if (OS.Constants.Sys.Name == "Darwin") {
+      executeSoon(aCallback);
+      return;
+    }
+
+    menuPopup.addEventListener("popuphidden", function onPopupHidden() {
+      menuPopup.removeEventListener("popuphidden", onPopupHidden, false);
+      executeSoon(aCallback);
+    }, false);
+
+    executeSoon(function() {
+      editMenu.open = false;
+    });
+  };
+
+  let openMenu = function(aCallback) {
+    if (OS.Constants.Sys.Name == "Darwin") {
+      goUpdateGlobalEditMenuItems();
+      // On OSX, we have a native menu, so it has to be updated. In single process browsers,
+      // this happens synchronously, but in e10s, we have to wait for the main thread
+      // to deal with it for us. 1 second should be plenty of time.
+      setTimeout(aCallback, 1000);
+      return;
+    }
+
+    menuPopup.addEventListener("popupshown", function onPopupShown() {
+      menuPopup.removeEventListener("popupshown", onPopupShown, false);
+      executeSoon(aCallback);
+    }, false);
+
+    executeSoon(function() {
+      editMenu.open = true;
+    });
+  };
+
+  yield BrowserTestUtils.withNewTab({ gBrowser: gBrowser, url: "about:blank" }, function* (browser) {
+    let menu_cut_disabled, menu_copy_disabled;
+
+    yield BrowserTestUtils.loadURI(browser, "data:text/html,<div>hello!</div>");
+    browser.focus();
+    yield new Promise(resolve => waitForFocus(resolve, window));
+    yield new Promise(openMenu);
+    menu_cut_disabled = menuPopup.querySelector("#menu_cut").getAttribute('disabled') == "true";
+    is(menu_cut_disabled, false, "menu_cut should be enabled");
+    menu_copy_disabled = menuPopup.querySelector("#menu_copy").getAttribute('disabled') == "true";
+    is(menu_copy_disabled, false, "menu_copy should be enabled");
+    yield new Promise(closeMenu);
+
+    yield BrowserTestUtils.loadURI(browser, "data:text/html,<div contentEditable='true'>hello!</div>");
+    browser.focus();
+    yield new Promise(resolve => waitForFocus(resolve, window));
+    yield new Promise(openMenu);
+    menu_cut_disabled = menuPopup.querySelector("#menu_cut").getAttribute('disabled') == "true";
+    is(menu_cut_disabled, false, "menu_cut should be enabled");
+    menu_copy_disabled = menuPopup.querySelector("#menu_copy").getAttribute('disabled') == "true";
+    is(menu_copy_disabled, false, "menu_copy should be enabled");
+    yield new Promise(closeMenu);
+
+    yield BrowserTestUtils.loadURI(browser, "about:preferences");
+    browser.focus();
+    yield new Promise(resolve => waitForFocus(resolve, window));
+    yield new Promise(openMenu);
+    menu_cut_disabled = menuPopup.querySelector("#menu_cut").getAttribute('disabled') == "true";
+    is(menu_cut_disabled, true, "menu_cut should be disabled");
+    menu_copy_disabled = menuPopup.querySelector("#menu_copy").getAttribute('disabled') == "true";
+    is(menu_copy_disabled, true, "menu_copy should be disabled");
+    yield new Promise(closeMenu);
+  });
+});