Bug 1815969 - part 1: Redesign EditorBase::FireClipboardEvent r=m_kato

It does not handle critical cases properly, and it uses an out-param.
We can rewrite it with `Result`.  However, unfortunately,
`nsCopySupport::FireClipboardEvent` does not return error.  Therefore,
the root callers still need to treat the error cases as "canceled".

Differential Revision: https://phabricator.services.mozilla.com/D176737
This commit is contained in:
Masayuki Nakano 2023-05-10 06:44:52 +00:00
parent 32c74afbb2
commit 294c506854
6 changed files with 217 additions and 49 deletions

View File

@ -243,7 +243,7 @@ class HyperTextAccessible : public AccessibleWrap,
MOZ_CAN_RUN_SCRIPT_BOUNDARY void ReplaceText(const nsAString& aText);
MOZ_CAN_RUN_SCRIPT_BOUNDARY void InsertText(const nsAString& aText,
int32_t aPosition);
void CopyText(int32_t aStartPos, int32_t aEndPos);
MOZ_CAN_RUN_SCRIPT_BOUNDARY void CopyText(int32_t aStartPos, int32_t aEndPos);
MOZ_CAN_RUN_SCRIPT_BOUNDARY void CutText(int32_t aStartPos, int32_t aEndPos);
MOZ_CAN_RUN_SCRIPT_BOUNDARY void DeleteText(int32_t aStartPos,
int32_t aEndPos);

View File

@ -33,6 +33,7 @@
#include "ErrorList.h"
#include "gfxFontUtils.h" // for gfxFontUtils
#include "mozilla/Assertions.h"
#include "mozilla/intl/BidiEmbeddingLevel.h"
#include "mozilla/BasePrincipal.h" // for BasePrincipal
#include "mozilla/CheckedInt.h" // for CheckedInt
@ -1578,35 +1579,64 @@ bool EditorBase::CheckForClipboardCommandListener(
return false;
}
bool EditorBase::FireClipboardEvent(EventMessage aEventMessage,
int32_t aClipboardType,
bool* aActionTaken) {
Result<EditorBase::ClipboardEventResult, nsresult>
EditorBase::DispatchClipboardEventAndUpdateClipboard(EventMessage aEventMessage,
int32_t aClipboardType) {
MOZ_ASSERT(IsEditActionDataAvailable());
if (aEventMessage == ePaste) {
CommitComposition();
if (NS_WARN_IF(Destroyed())) {
return Err(NS_ERROR_EDITOR_DESTROYED);
}
}
RefPtr<PresShell> presShell = GetPresShell();
if (NS_WARN_IF(!presShell)) {
return false;
return Err(NS_ERROR_NOT_AVAILABLE);
}
RefPtr<Selection> sel = &SelectionRef();
if (IsHTMLEditor() && aEventMessage == eCopy && sel->IsCollapsed()) {
// If we don't have a usable selection for copy and we're an HTML editor
// (which is global for the document) try to use the last focused selection
// instead.
sel = nsCopySupport::GetSelectionForCopy(GetDocument());
}
const RefPtr<Selection> sel = [&]() {
if (IsHTMLEditor() && aEventMessage == eCopy &&
SelectionRef().IsCollapsed()) {
// If we don't have a usable selection for copy and we're an HTML
// editor (which is global for the document) try to use the last
// focused selection instead.
return nsCopySupport::GetSelectionForCopy(GetDocument());
}
return do_AddRef(&SelectionRef());
}();
const bool clipboardEventCanceled = !nsCopySupport::FireClipboardEvent(
aEventMessage, aClipboardType, presShell, sel, aActionTaken);
bool actionTaken = false;
const bool doDefault = nsCopySupport::FireClipboardEvent(
aEventMessage, aClipboardType, presShell, sel, &actionTaken);
NotifyOfDispatchingClipboardEvent();
// If the event handler caused the editor to be destroyed, return false.
// Otherwise return true if the event was not cancelled.
return !clipboardEventCanceled && !mDidPreDestroy;
if (NS_WARN_IF(Destroyed())) {
return Err(NS_ERROR_EDITOR_DESTROYED);
}
if (doDefault) {
MOZ_ASSERT(actionTaken);
return ClipboardEventResult::DoDefault;
}
// If we handle a "paste" and nsCopySupport::FireClipboardEvent sets
// actionTaken to "false" means that it's an error. Otherwise, the "paste"
// event is just canceled.
if (aEventMessage == ePaste) {
return actionTaken ? ClipboardEventResult::DefaultPreventedOfPaste
: ClipboardEventResult::IgnoredOrError;
}
// If we handle a "copy", actionTaken is set to true only when
// nsCopySupport::FireClipboardEvent does not meet an error.
// If we handle a "cut", actionTaken is set to true only when
// nsCopySupport::FireClipboardEvent does not meet an error and
// - the selection is collapsed in editable elements when the event is not
// canceled.
// - the event is canceled but update the clipboard with the dataTransfer
// of the event.
return actionTaken ? ClipboardEventResult::CopyOrCutHandled
: ClipboardEventResult::IgnoredOrError;
}
NS_IMETHODIMP EditorBase::Cut() {
@ -1621,10 +1651,26 @@ nsresult EditorBase::CutAsAction(nsIPrincipal* aPrincipal) {
return NS_ERROR_NOT_INITIALIZED;
}
bool actionTaken = false;
if (!FireClipboardEvent(eCut, nsIClipboard::kGlobalClipboard, &actionTaken)) {
return EditorBase::ToGenericNSResult(
actionTaken ? NS_OK : NS_ERROR_EDITOR_ACTION_CANCELED);
{
Result<ClipboardEventResult, nsresult> ret =
DispatchClipboardEventAndUpdateClipboard(
eCut, nsIClipboard::kGlobalClipboard);
if (MOZ_UNLIKELY(ret.isErr())) {
NS_WARNING(
"EditorBase::DispatchClipboardEventAndUpdateClipboard(eCut, "
"nsIClipboard::kGlobalClipboard) failed");
return EditorBase::ToGenericNSResult(ret.unwrapErr());
}
switch (ret.unwrap()) {
case ClipboardEventResult::DoDefault:
break;
case ClipboardEventResult::CopyOrCutHandled:
return NS_OK;
case ClipboardEventResult::IgnoredOrError:
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
case ClipboardEventResult::DefaultPreventedOfPaste:
MOZ_ASSERT_UNREACHABLE("Invalid result for eCut");
}
}
// Dispatch "beforeinput" event after dispatching "cut" event.
@ -1678,11 +1724,25 @@ NS_IMETHODIMP EditorBase::Copy() {
return NS_ERROR_NOT_INITIALIZED;
}
bool actionTaken = false;
FireClipboardEvent(eCopy, nsIClipboard::kGlobalClipboard, &actionTaken);
return EditorBase::ToGenericNSResult(
actionTaken ? NS_OK : NS_ERROR_EDITOR_ACTION_CANCELED);
Result<ClipboardEventResult, nsresult> ret =
DispatchClipboardEventAndUpdateClipboard(eCopy,
nsIClipboard::kGlobalClipboard);
if (MOZ_UNLIKELY(ret.isErr())) {
NS_WARNING(
"EditorBase::DispatchClipboardEventAndUpdateClipboard(eCopy, "
"nsIClipboard::kGlobalClipboard) failed");
return EditorBase::ToGenericNSResult(ret.unwrapErr());
}
switch (ret.unwrap()) {
case ClipboardEventResult::DoDefault:
case ClipboardEventResult::CopyOrCutHandled:
return NS_OK;
case ClipboardEventResult::IgnoredOrError:
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
case ClipboardEventResult::DefaultPreventedOfPaste:
MOZ_ASSERT_UNREACHABLE("Invalid result for eCopy");
}
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP EditorBase::CanCopy(bool* aCanCopy) {

View File

@ -2619,18 +2619,33 @@ class EditorBase : public nsIEditor,
nsAtom* aCommand, EventMessage aEventMessage) const;
/**
* FireClipboardEvent() may dispatch a clipboard event.
* DispatchClipboardEventAndUpdateClipboard() may dispatch a clipboard event
* and update clipboard if aEventMessage is eCopy or eCut.
*
* @param aEventMessage The event message which may be set to the
* dispatching event.
* @param aClipboardType Working with global clipboard or selection.
* @param aActionTaken [optional][out] If set to non-nullptr, will be
* set to true if the action for the event is
* handled or prevented default.
* @return false if dispatching event is canceled.
*/
bool FireClipboardEvent(EventMessage aEventMessage, int32_t aClipboardType,
bool* aActionTaken = nullptr);
enum class ClipboardEventResult {
// We have met an error in nsCopySupport::FireClipboardEvent,
// or, default of dispatched event is NOT prevented, the event is "cut"
// and the event target is not editable.
IgnoredOrError,
// A "paste" event is dispatched and prevented its default.
DefaultPreventedOfPaste,
// Default of a "copy" or "cut" event is prevented but the clipboard is
// updated unless the dataTransfer of the event is cleared by the listener.
// Or, default of the event is NOT prevented but selection is collapsed
// when the event target is editable or the event is "copy".
CopyOrCutHandled,
// A clipboard event is maybe dispatched and not canceled by the web app.
// In this case, the clipboard has been updated if aEventMessage is eCopy
// or eCut.
DoDefault,
};
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<ClipboardEventResult, nsresult>
DispatchClipboardEventAndUpdateClipboard(EventMessage aEventMessage,
int32_t aClipboardType);
private:
nsCOMPtr<nsISelectionController> mSelectionController;

View File

@ -4,7 +4,6 @@
* 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/. */
#include "ErrorList.h"
#include "HTMLEditor.h"
#include <string.h>
@ -19,6 +18,7 @@
#include "SelectionState.h"
#include "WSRunObject.h"
#include "ErrorList.h"
#include "mozilla/dom/Comment.h"
#include "mozilla/dom/DataTransfer.h"
#include "mozilla/dom/Document.h"
@ -2275,8 +2275,22 @@ nsresult HTMLEditor::PasteAsAction(int32_t aClipboardType,
}
if (aDispatchPasteEvent) {
if (!FireClipboardEvent(ePaste, aClipboardType)) {
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
Result<ClipboardEventResult, nsresult> ret =
DispatchClipboardEventAndUpdateClipboard(ePaste, aClipboardType);
if (MOZ_UNLIKELY(ret.isErr())) {
NS_WARNING(
"EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste) "
"failed");
return EditorBase::ToGenericNSResult(ret.unwrapErr());
}
switch (ret.inspect()) {
case ClipboardEventResult::DoDefault:
break;
case ClipboardEventResult::DefaultPreventedOfPaste:
case ClipboardEventResult::IgnoredOrError:
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
case ClipboardEventResult::CopyOrCutHandled:
MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
}
} else {
// The caller must already have dispatched a "paste" event.
@ -2428,8 +2442,25 @@ nsresult HTMLEditor::PasteTransferableAsAction(nsITransferable* aTransferable,
// Use an invalid value for the clipboard type as data comes from
// aTransferable and we don't currently implement a way to put that in the
// data transfer yet.
if (!FireClipboardEvent(ePaste, nsIClipboard::kGlobalClipboard)) {
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
{
Result<ClipboardEventResult, nsresult> ret =
DispatchClipboardEventAndUpdateClipboard(
ePaste, nsIClipboard::kGlobalClipboard);
if (MOZ_UNLIKELY(ret.isErr())) {
NS_WARNING(
"EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste, "
"nsIClipboard::kGlobalClipboard) failed");
return EditorBase::ToGenericNSResult(ret.unwrapErr());
}
switch (ret.inspect()) {
case ClipboardEventResult::DoDefault:
break;
case ClipboardEventResult::DefaultPreventedOfPaste:
case ClipboardEventResult::IgnoredOrError:
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
case ClipboardEventResult::CopyOrCutHandled:
MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
}
}
// Dispatch "beforeinput" event after "paste" event.
@ -2476,8 +2507,25 @@ nsresult HTMLEditor::PasteNoFormattingAsAction(int32_t aSelectionType,
editActionData.InitializeDataTransferWithClipboard(
SettingDataTransfer::eWithoutFormat, aSelectionType);
if (!FireClipboardEvent(ePasteNoFormatting, aSelectionType)) {
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
{
Result<ClipboardEventResult, nsresult> ret =
DispatchClipboardEventAndUpdateClipboard(ePasteNoFormatting,
aSelectionType);
if (MOZ_UNLIKELY(ret.isErr())) {
NS_WARNING(
"EditorBase::DispatchClipboardEventAndUpdateClipboard("
"ePasteNoFormatting) failed");
return EditorBase::ToGenericNSResult(ret.unwrapErr());
}
switch (ret.inspect()) {
case ClipboardEventResult::DoDefault:
break;
case ClipboardEventResult::DefaultPreventedOfPaste:
case ClipboardEventResult::IgnoredOrError:
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
case ClipboardEventResult::CopyOrCutHandled:
MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
}
}
// Dispatch "beforeinput" event after "paste" event. And perhaps, before
@ -2645,8 +2693,22 @@ nsresult HTMLEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
}
if (aDispatchPasteEvent) {
if (!FireClipboardEvent(ePaste, aClipboardType)) {
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
Result<ClipboardEventResult, nsresult> ret =
DispatchClipboardEventAndUpdateClipboard(ePaste, aClipboardType);
if (MOZ_UNLIKELY(ret.isErr())) {
NS_WARNING(
"EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste) "
"failed");
return EditorBase::ToGenericNSResult(ret.unwrapErr());
}
switch (ret.inspect()) {
case ClipboardEventResult::DoDefault:
break;
case ClipboardEventResult::DefaultPreventedOfPaste:
case ClipboardEventResult::IgnoredOrError:
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
case ClipboardEventResult::CopyOrCutHandled:
MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
}
} else {
// The caller must already have dispatched a "paste" event.

View File

@ -170,8 +170,22 @@ nsresult TextEditor::PasteAsAction(int32_t aClipboardType,
}
if (aDispatchPasteEvent) {
if (!FireClipboardEvent(ePaste, aClipboardType)) {
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
Result<ClipboardEventResult, nsresult> ret =
DispatchClipboardEventAndUpdateClipboard(ePaste, aClipboardType);
if (MOZ_UNLIKELY(ret.isErr())) {
NS_WARNING(
"EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste) "
"failed");
return EditorBase::ToGenericNSResult(ret.unwrapErr());
}
switch (ret.inspect()) {
case ClipboardEventResult::DoDefault:
break;
case ClipboardEventResult::DefaultPreventedOfPaste:
case ClipboardEventResult::IgnoredOrError:
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
case ClipboardEventResult::CopyOrCutHandled:
MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
}
} else {
// The caller must already have dispatched a "paste" event.
@ -235,11 +249,27 @@ nsresult TextEditor::PasteTransferableAsAction(nsITransferable* aTransferable,
return NS_ERROR_NOT_INITIALIZED;
}
// Use an invalid value for the clipboard type as data comes from
// aTransferable and we don't currently implement a way to put that in the
// data transfer yet.
if (!FireClipboardEvent(ePaste, -1)) {
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
{
// Use an invalid value for the clipboard type as data comes from
// aTransferable and we don't currently implement a way to put that in the
// data transfer yet.
Result<ClipboardEventResult, nsresult> ret =
DispatchClipboardEventAndUpdateClipboard(ePaste, -1);
if (MOZ_UNLIKELY(ret.isErr())) {
NS_WARNING(
"EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste, -1) "
"failed");
return EditorBase::ToGenericNSResult(ret.unwrapErr());
}
switch (ret.inspect()) {
case ClipboardEventResult::DoDefault:
break;
case ClipboardEventResult::DefaultPreventedOfPaste:
case ClipboardEventResult::IgnoredOrError:
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
case ClipboardEventResult::CopyOrCutHandled:
MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
}
}
if (!IsModifiable()) {

View File

@ -378,6 +378,7 @@ interface nsIEditor : nsISupports
* What about mixed selections?
* What are the clipboard formats?
*/
[can_run_script]
void copy();
/**