diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp index 5ff462de0f24..0aaecf2f307d 100644 --- a/editor/libeditor/EditorBase.cpp +++ b/editor/libeditor/EditorBase.cpp @@ -1248,7 +1248,7 @@ NS_IMETHODIMP EditorBase::CanCut(bool* aCanCut) { if (NS_WARN_IF(!aCanCut)) { return NS_ERROR_INVALID_ARG; } - *aCanCut = AsTextEditor()->IsCutCommandEnabled(); + *aCanCut = MOZ_KnownLive(AsTextEditor())->IsCutCommandEnabled(); return NS_OK; } @@ -1258,7 +1258,7 @@ NS_IMETHODIMP EditorBase::CanCopy(bool* aCanCopy) { if (NS_WARN_IF(!aCanCopy)) { return NS_ERROR_INVALID_ARG; } - *aCanCopy = AsTextEditor()->IsCopyCommandEnabled(); + *aCanCopy = MOZ_KnownLive(AsTextEditor())->IsCopyCommandEnabled(); return NS_OK; } diff --git a/editor/libeditor/TextEditor.cpp b/editor/libeditor/TextEditor.cpp index 4cac42a23938..ee99d36f7ae0 100644 --- a/editor/libeditor/TextEditor.cpp +++ b/editor/libeditor/TextEditor.cpp @@ -13,9 +13,11 @@ #include "PlaceholderTransaction.h" #include "gfxFontUtils.h" #include "mozilla/Assertions.h" +#include "mozilla/ContentEvents.h" #include "mozilla/ContentIterator.h" #include "mozilla/EditAction.h" #include "mozilla/EditorDOMPoint.h" +#include "mozilla/EventDispatcher.h" #include "mozilla/HTMLEditor.h" #include "mozilla/IMEStateManager.h" #include "mozilla/LookAndFeel.h" @@ -54,6 +56,7 @@ #include "nsIWeakReferenceUtils.h" #include "nsNameSpaceManager.h" #include "nsLiteralString.h" +#include "nsPresContext.h" #include "nsReadableUtils.h" #include "nsServiceManagerUtils.h" #include "nsString.h" @@ -1214,17 +1217,64 @@ bool TextEditor::AreClipboardCommandsUnconditionallyEnabled() const { return document && document->AreClipboardCommandsUnconditionallyEnabled(); } +bool TextEditor::CheckForClipboardCommandListener( + nsAtom* aCommand, EventMessage aEventMessage) const { + RefPtr document = GetDocument(); + if (!document) { + return false; + } + + // We exclude XUL and chrome docs here to maintain current behavior where + // in these cases the editor element alone is expected to handle clipboard + // command availability. + if (!document->AreClipboardCommandsUnconditionallyEnabled()) { + return false; + } + + // So in web content documents, "unconditionally" enabled Cut/Copy are not + // really unconditional; they're enabled if there is a listener that wants + // to handle them. What they're not conditional on here is whether there is + // currently a selection in the editor. + RefPtr presShell = document->GetObservingPresShell(); + if (!presShell) { + return false; + } + RefPtr presContext = presShell->GetPresContext(); + if (!presContext) { + return false; + } + + RefPtr et = GetDOMEventTarget(); + while (et) { + EventListenerManager* elm = et->GetOrCreateListenerManager(); + if (elm && elm->HasListenersFor(aCommand)) { + return true; + } + InternalClipboardEvent event(true, aEventMessage); + EventChainPreVisitor visitor(presContext, &event, nullptr, + nsEventStatus_eIgnore, false, et); + et->GetEventTargetParent(visitor); + et = visitor.GetParentTarget(); + } + + return false; +} + bool TextEditor::IsCutCommandEnabled() const { AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return false; } - if (AreClipboardCommandsUnconditionallyEnabled()) { + if (IsModifiable() && IsCopyToClipboardAllowedInternal()) { return true; } - return IsModifiable() && IsCopyToClipboardAllowedInternal(); + // If there's an event listener for "cut", we always enable the command + // as we don't really know what the listener may want to do in response. + // We look up the event target chain for a possible listener on a parent + // in addition to checking the immediate target. + return CheckForClipboardCommandListener(nsGkAtoms::oncut, eCut); } NS_IMETHODIMP TextEditor::Copy() { @@ -1246,11 +1296,12 @@ bool TextEditor::IsCopyCommandEnabled() const { return false; } - if (AreClipboardCommandsUnconditionallyEnabled()) { + if (IsCopyToClipboardAllowedInternal()) { return true; } - return IsCopyToClipboardAllowedInternal(); + // Like "cut", always enable "copy" if there's a listener. + return CheckForClipboardCommandListener(nsGkAtoms::oncopy, eCopy); } bool TextEditor::CanDeleteSelection() const { diff --git a/editor/libeditor/TextEditor.h b/editor/libeditor/TextEditor.h index a08f57952cdd..14c93eac6d0e 100644 --- a/editor/libeditor/TextEditor.h +++ b/editor/libeditor/TextEditor.h @@ -96,7 +96,7 @@ class TextEditor : public EditorBase, public nsITimerCallback, public nsINamed { * disabled. This always returns true if we're in non-chrome HTML/XHTML * document. Otherwise, same as the result of `IsCopyToClipboardAllowed()`. */ - bool IsCutCommandEnabled() const; + MOZ_CAN_RUN_SCRIPT bool IsCutCommandEnabled() const; NS_IMETHOD Copy() override; @@ -105,7 +105,7 @@ class TextEditor : public EditorBase, public nsITimerCallback, public nsINamed { * This always returns true if we're in non-chrome HTML/XHTML document. * Otherwise, same as the result of `IsCopyToClipboardAllowed()`. */ - bool IsCopyCommandEnabled() const; + MOZ_CAN_RUN_SCRIPT bool IsCopyCommandEnabled() const; /** * IsCopyToClipboardAllowed() returns true if the selected content can @@ -786,6 +786,13 @@ class TextEditor : public EditorBase, public nsITimerCallback, public nsINamed { bool aNotify, bool aForceStartMasking); + /** + * Helper for Is{Cut|Copy}CommandEnabled. + * Look for a listener for the given command, including up the target chain. + */ + MOZ_CAN_RUN_SCRIPT bool CheckForClipboardCommandListener( + nsAtom* aCommand, EventMessage aEventMessage) const; + protected: mutable nsCOMPtr mCachedDocumentEncoder; diff --git a/editor/nsIEditor.idl b/editor/nsIEditor.idl index ce1ef09f9fef..f0be7081449a 100644 --- a/editor/nsIEditor.idl +++ b/editor/nsIEditor.idl @@ -328,6 +328,7 @@ interface nsIEditor : nsISupports * HTML/XHTML document. * FYI: Current user in script is only BlueGriffon. */ + [can_run_script] boolean canCut(); /** copy the currently selected text, putting it into the OS clipboard @@ -344,6 +345,7 @@ interface nsIEditor : nsISupports * HTML/XHTML document. * FYI: Current user in script is only BlueGriffon. */ + [can_run_script] boolean canCopy(); /** paste the text in the OS clipboard at the cursor position, replacing