Bug 1012662 - Part 2 - Updates to clipboard command controllers to match cut/copy action spec. r=ehsan
authorMichael Layzell <michael@thelayzells.com>
Wed, 13 May 2015 08:51:00 +0200
changeset 278455 fd8529bd0d4de011a36f8bcc16f9b1674fa0e22f
parent 278454 cbd4999d7e9771a571a499ea0dfc5ebb304c9a54
child 278456 cb118b19a9021eb9a09d59fe89d678f7c1da82b3
push id897
push userjlund@mozilla.com
push dateMon, 14 Sep 2015 18:56:12 +0000
treeherdermozilla-release@9411e2d2b214 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs1012662
milestone41.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 1012662 - Part 2 - Updates to clipboard command controllers to match cut/copy action spec. r=ehsan
dom/base/nsCopySupport.cpp
dom/base/nsCopySupport.h
dom/base/nsGlobalWindowCommands.cpp
dom/tests/mochitest/general/test_clipboard_events.html
editor/libeditor/nsPlaintextEditor.cpp
editor/libeditor/nsPlaintextEditor.h
--- a/dom/base/nsCopySupport.cpp
+++ b/dom/base/nsCopySupport.cpp
@@ -615,18 +615,23 @@ IsSelectionInsideRuby(nsISelection* aSel
     if (!IsInsideRuby(n)) {
       return false;
     }
   }
   return true;
 }
 
 bool
-nsCopySupport::FireClipboardEvent(int32_t aType, int32_t aClipboardType, nsIPresShell* aPresShell, nsISelection* aSelection)
+nsCopySupport::FireClipboardEvent(int32_t aType, int32_t aClipboardType, nsIPresShell* aPresShell,
+                                  nsISelection* aSelection, bool* aActionTaken)
 {
+  if (aActionTaken) {
+    *aActionTaken = false;
+  }
+
   NS_ASSERTION(aType == NS_CUT || aType == NS_COPY || aType == NS_PASTE,
                "Invalid clipboard event type");
 
   nsCOMPtr<nsIPresShell> presShell = aPresShell;
   if (!presShell)
     return false;
 
   nsCOMPtr<nsIDocument> doc = presShell->GetDocument();
@@ -641,24 +646,16 @@ nsCopySupport::FireClipboardEvent(int32_
   nsCOMPtr<nsIContent> content;
   nsCOMPtr<nsISelection> sel = aSelection;
   if (!sel)
     content = GetSelectionForCopy(doc, getter_AddRefs(sel));
 
   // retrieve the event target node from the start of the selection
   nsresult rv;
   if (sel) {
-    // Only cut or copy when there is an uncollapsed selection
-    if (aType == NS_CUT || aType == NS_COPY) {
-      bool isCollapsed;
-      sel->GetIsCollapsed(&isCollapsed);
-      if (isCollapsed)
-        return false;
-    }
-
     nsCOMPtr<nsIDOMRange> range;
     rv = sel->GetRangeAt(0, getter_AddRefs(range));
     if (NS_SUCCEEDED(rv) && range) {
       nsCOMPtr<nsIDOMNode> startContainer;
       range->GetStartContainer(getter_AddRefs(startContainer));
       if (startContainer)
         content = do_QueryInterface(startContainer);
     }
@@ -691,55 +688,81 @@ nsCopySupport::FireClipboardEvent(int32_
     nsEventStatus status = nsEventStatus_eIgnore;
     InternalClipboardEvent evt(true, aType);
     evt.clipboardData = clipboardData;
     EventDispatcher::Dispatch(content, presShell->GetPresContext(), &evt,
                               nullptr, &status);
     // If the event was cancelled, don't do the clipboard operation
     doDefault = (status != nsEventStatus_eConsumeNoDefault);
   }
-  
+
   // No need to do anything special during a paste. Either an event listener
   // took care of it and cancelled the event, or the caller will handle it.
   // Return true to indicate that the event wasn't cancelled.
   if (aType == NS_PASTE) {
     // Clear and mark the clipboardData as readonly. This prevents someone
     // from reading the clipboard contents after the paste event has fired.
     if (clipboardData) {
       clipboardData->ClearAll();
       clipboardData->SetReadOnly();
     }
 
+    if (aActionTaken) {
+      *aActionTaken = true;
+    }
     return doDefault;
   }
 
   // Update the presentation in case the event handler modified the selection,
   // see bug 602231.
   presShell->FlushPendingNotifications(Flush_Frames);
   if (presShell->IsDestroying())
     return false;
 
   // if the event was not cancelled, do the default copy. If the event was cancelled,
   // use the data added to the data transfer and copy that instead.
   uint32_t count = 0;
   if (doDefault) {
-    // get the data from the selection if any
-    bool isCollapsed;
-    sel->GetIsCollapsed(&isCollapsed);
-    if (isCollapsed) {
-      return false;
+    // find the focused node
+    nsCOMPtr<nsIContent> srcNode = content;
+    if (content->IsInNativeAnonymousSubtree()) {
+      srcNode = content->FindFirstNonChromeOnlyAccessContent();
+    }
+
+    // check if we are looking at a password input
+    nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(srcNode);
+    if (formControl) {
+      if (formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
+        return false;
+      }
     }
-    // XXX Code which decides whether we should copy text with ruby
-    // annotation is currenct depending on whether each range of the
-    // selection is inside a same ruby container. But we really should
-    // expose the full functionality in browser. See bug 1130891.
-    bool withRubyAnnotation = IsSelectionInsideRuby(sel);
-    // call the copy code
-    rv = HTMLCopy(sel, doc, aClipboardType, withRubyAnnotation);
-    if (NS_FAILED(rv)) {
+
+    // when cutting non-editable content, do nothing
+    // XXX this is probably the wrong editable flag to check
+    if (aType != NS_CUT || content->IsEditable()) {
+      // get the data from the selection if any
+      bool isCollapsed;
+      sel->GetIsCollapsed(&isCollapsed);
+      if (isCollapsed) {
+        if (aActionTaken) {
+          *aActionTaken = true;
+        }
+        return false;
+      }
+      // XXX Code which decides whether we should copy text with ruby
+      // annotation is currenct depending on whether each range of the
+      // selection is inside a same ruby container. But we really should
+      // expose the full functionality in browser. See bug 1130891.
+      bool withRubyAnnotation = IsSelectionInsideRuby(sel);
+      // call the copy code
+      rv = HTMLCopy(sel, doc, aClipboardType, withRubyAnnotation);
+      if (NS_FAILED(rv)) {
+        return false;
+      }
+    } else {
       return false;
     }
   } else if (clipboardData) {
     // check to see if any data was put on the data transfer.
     clipboardData->GetMozItemCount(&count);
     if (count) {
       nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1"));
       NS_ENSURE_TRUE(clipboard, false);
@@ -758,10 +781,13 @@ nsCopySupport::FireClipboardEvent(int32_
   }
 
   // Now that we have copied, update the clipboard commands. This should have
   // the effect of updating the enabled state of the paste menu item.
   if (doDefault || count) {
     piWindow->UpdateCommands(NS_LITERAL_STRING("clipboard"), nullptr, 0);
   }
 
+  if (aActionTaken) {
+    *aActionTaken = true;
+  }
   return doDefault;
 }
--- a/dom/base/nsCopySupport.h
+++ b/dom/base/nsCopySupport.h
@@ -78,17 +78,21 @@ class nsCopySupport
      * have no default behaviour but true will be returned. It is expected
      * that the caller will execute any needed default paste behaviour. Also,
      * note that this method only copies text to the clipboard, the caller is
      * responsible for removing the content during a cut operation if true is
      * returned.
      *
      * aClipboardType specifies which clipboard to use, from nsIClipboard.
      *
+     * If aActionTaken is non-NULL, it will be set to true if an action was
+     * taken, whether it be the default action or the default being prevented.
+     *
      * If the event is cancelled or an error occurs, false will be returned.
      */
     static bool FireClipboardEvent(int32_t aType,
                                    int32_t aClipboardType,
                                    nsIPresShell* aPresShell,
-                                   nsISelection* aSelection);
+                                   nsISelection* aSelection,
+                                   bool* aActionTaken = nullptr);
 };
 
 #endif
--- a/dom/base/nsGlobalWindowCommands.cpp
+++ b/dom/base/nsGlobalWindowCommands.cpp
@@ -495,39 +495,49 @@ nsClipboardCommand::IsCommandEnabled(con
   nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
   *outCmdEnabled = nsCopySupport::CanCopy(doc);
   return NS_OK;
 }
 
 nsresult
 nsClipboardCommand::DoCommand(const char *aCommandName, nsISupports *aContext)
 {
-  if (strcmp(aCommandName, "cmd_copy") &&
+  if (strcmp(aCommandName, "cmd_cut") &&
+      strcmp(aCommandName, "cmd_copy") &&
       strcmp(aCommandName, "cmd_copyAndCollapseToEnd"))
     return NS_OK;
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aContext);
   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
   nsIDocShell *docShell = window->GetDocShell();
   NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
   NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
 
-  nsCopySupport::FireClipboardEvent(NS_COPY, nsIClipboard::kGlobalClipboard, presShell, nullptr);
+  int32_t eventType = NS_COPY;
+  if (strcmp(aCommandName, "cmd_cut") == 0) {
+    eventType = NS_CUT;
+  }
+
+  bool actionTaken = false;
+  nsCopySupport::FireClipboardEvent(eventType, nsIClipboard::kGlobalClipboard, presShell, nullptr, &actionTaken);
 
   if (!strcmp(aCommandName, "cmd_copyAndCollapseToEnd")) {
     dom::Selection *sel =
       presShell->GetCurrentSelection(nsISelectionController::SELECTION_NORMAL);
     NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE);
     sel->CollapseToEnd();
   }
 
-  return NS_OK;
+  if (actionTaken) {
+    return NS_OK;
+  }
+  return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsClipboardCommand::GetCommandStateParams(const char *aCommandName,
                                               nsICommandParams *aParams, nsISupports *aCommandContext)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -145,17 +145,17 @@ function test_dom_oncut() {
   // Setup an oncut event handler, fire cut.  Ensure that the event handler
   // was called.  The <div> doesn't handle a cut, so ensure that the
   // clipboard text is clipboardInitialValue, NOT "CONTENT TEXT".
   selectContentDiv();
   var oncut_fired = false;
   content.oncut = function() { oncut_fired = true; };
   try {
     synthesizeKey("x", {accelKey: 1});
-    ok(!oncut_fired, "cut event firing on DOM element")
+    ok(oncut_fired, "cut event firing on DOM element")
     is(getClipboardText(), clipboardInitialValue,
       "cut on DOM element did not modify clipboard");
   } finally {
     content.oncut = null;
   }
 }
 
 
--- a/editor/libeditor/nsPlaintextEditor.cpp
+++ b/editor/libeditor/nsPlaintextEditor.cpp
@@ -1163,55 +1163,59 @@ nsPlaintextEditor::CanCutOrCopy(Password
   if (aPasswordFieldAllowed == ePasswordFieldNotAllowed &&
       IsPasswordEditor())
     return false;
 
   return !selection->Collapsed();
 }
 
 bool
-nsPlaintextEditor::FireClipboardEvent(int32_t aType, int32_t aSelectionType)
+nsPlaintextEditor::FireClipboardEvent(int32_t aType, int32_t aSelectionType, bool* aActionTaken)
 {
   if (aType == NS_PASTE)
     ForceCompositionEnd();
 
   nsCOMPtr<nsIPresShell> presShell = GetPresShell();
   NS_ENSURE_TRUE(presShell, false);
 
   nsRefPtr<Selection> selection = GetSelection();
   if (!selection) {
     return false;
   }
 
-  if (!nsCopySupport::FireClipboardEvent(aType, aSelectionType, presShell, selection))
+  if (!nsCopySupport::FireClipboardEvent(aType, aSelectionType, presShell, selection, aActionTaken))
     return false;
 
   // If the event handler caused the editor to be destroyed, return false.
   // Otherwise return true to indicate that the event was not cancelled.
   return !mDidPreDestroy;
 }
 
 NS_IMETHODIMP nsPlaintextEditor::Cut()
 {
-  if (FireClipboardEvent(NS_CUT, nsIClipboard::kGlobalClipboard))
-    return DeleteSelection(eNone, eStrip);
-  return NS_OK;
+  bool actionTaken = false;
+  if (FireClipboardEvent(NS_CUT, nsIClipboard::kGlobalClipboard, &actionTaken)) {
+    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);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsPlaintextEditor::Copy()
 {
-  FireClipboardEvent(NS_COPY, nsIClipboard::kGlobalClipboard);
-  return NS_OK;
+  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);
   return NS_OK;
 }
--- a/editor/libeditor/nsPlaintextEditor.h
+++ b/editor/libeditor/nsPlaintextEditor.h
@@ -202,17 +202,17 @@ protected:
   /* small utility routine to test the eEditorReadonly bit */
   bool IsModifiable();
 
   enum PasswordFieldAllowed {
     ePasswordFieldAllowed,
     ePasswordFieldNotAllowed
   };
   bool CanCutOrCopy(PasswordFieldAllowed aPasswordFieldAllowed);
-  bool FireClipboardEvent(int32_t aType, int32_t aSelectionType);
+  bool FireClipboardEvent(int32_t aType, int32_t aSelectionType, bool* aActionTaken = nullptr);
 
   bool UpdateMetaCharset(nsIDOMDocument* aDocument,
                          const nsACString& aCharacterSet);
 
 // Data members
 protected:
 
   nsCOMPtr<nsIEditRules>        mRules;