mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 03:15:11 +00:00
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:
parent
32c74afbb2
commit
294c506854
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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()) {
|
||||
|
@ -378,6 +378,7 @@ interface nsIEditor : nsISupports
|
||||
* What about mixed selections?
|
||||
* What are the clipboard formats?
|
||||
*/
|
||||
[can_run_script]
|
||||
void copy();
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user