Skip to content

Commit

Permalink
Bug 1149826 - Part 1. Add eContentCommandReplaceText. r=masayuki
Browse files Browse the repository at this point in the history
When using autocorrect, we should use `insertReplacementText` according
to w3c/input-events#152. So I would like to
add eContentCommandReplaceText command for this.

Also, this command has an option that is source string text. When
processing text subsitution, parent process doesn't know whether
target replaced text is modified. So I add this option for check.

Differential Revision: https://phabricator.services.mozilla.com/D213511
  • Loading branch information
makotokato committed Jul 26, 2024
1 parent 16442cb commit 131eaf7
Show file tree
Hide file tree
Showing 25 changed files with 804 additions and 142 deletions.
16 changes: 13 additions & 3 deletions dom/base/nsDOMWindowUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2566,7 +2566,10 @@ nsDOMWindowUtils::SendSelectionSetEvent(uint32_t aOffset, uint32_t aLength,
NS_IMETHODIMP
nsDOMWindowUtils::SendContentCommandEvent(const nsAString& aType,
nsITransferable* aTransferable,
const nsAString& aString) {
const nsAString& aString,
uint32_t aOffset,
const nsAString& aReplaceSrcString,
uint32_t aAdditionalFlags) {
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) return NS_ERROR_FAILURE;
Expand All @@ -2586,6 +2589,8 @@ nsDOMWindowUtils::SendContentCommandEvent(const nsAString& aType,
msg = eContentCommandRedo;
} else if (aType.EqualsLiteral("insertText")) {
msg = eContentCommandInsertText;
} else if (aType.EqualsLiteral("replaceText")) {
msg = eContentCommandReplaceText;
} else if (aType.EqualsLiteral("pasteTransferable")) {
msg = eContentCommandPasteTransferable;
} else {
Expand All @@ -2595,8 +2600,13 @@ nsDOMWindowUtils::SendContentCommandEvent(const nsAString& aType,
WidgetContentCommandEvent event(true, msg, widget);
if (msg == eContentCommandInsertText) {
event.mString.emplace(aString);
}
if (msg == eContentCommandPasteTransferable) {
} else if (msg == eContentCommandReplaceText) {
event.mString.emplace(aString);
event.mSelection.mReplaceSrcString = aReplaceSrcString;
event.mSelection.mOffset = aOffset;
event.mSelection.mPreventSetSelection =
!!(aAdditionalFlags & CONTENT_COMMAND_FLAG_PREVENT_SET_SELECTION);
} else if (msg == eContentCommandPasteTransferable) {
event.mTransferable = aTransferable;
}

Expand Down
37 changes: 37 additions & 0 deletions dom/events/ContentEventHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "mozilla/IMEStateManager.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/Maybe.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/PresShell.h"
#include "mozilla/RangeBoundary.h"
#include "mozilla/RangeUtils.h"
Expand All @@ -23,6 +24,7 @@
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/HTMLUnknownElement.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/StaticRange.h"
#include "mozilla/dom/Text.h"
#include "nsCaret.h"
#include "nsCOMPtr.h"
Expand Down Expand Up @@ -857,6 +859,20 @@ nsresult ContentEventHandler::GenerateFlatTextContent(
return GenerateFlatTextContent(rawRange, aString, aLineBreakType);
}

nsresult ContentEventHandler::GenerateFlatTextContent(const nsRange* aRange,
nsString& aString) {
MOZ_ASSERT(aString.IsEmpty());

if (NS_WARN_IF(!aRange)) {
return NS_ERROR_FAILURE;
}

UnsafeSimpleRange rawRange;
rawRange.SetStartAndEnd(aRange);

return GenerateFlatTextContent(rawRange, aString, LINE_BREAK_TYPE_NATIVE);
}

template <typename NodeType, typename RangeBoundaryType>
nsresult ContentEventHandler::GenerateFlatTextContent(
const SimpleRangeBase<NodeType, RangeBoundaryType>& aSimpleRange,
Expand Down Expand Up @@ -1158,6 +1174,27 @@ nsresult ContentEventHandler::ExpandToClusterBoundary(
return NS_OK;
}

already_AddRefed<nsRange> ContentEventHandler::GetRangeFromFlatTextOffset(
WidgetContentCommandEvent* aEvent, uint32_t aOffset, uint32_t aLength) {
nsresult rv = InitCommon(aEvent->mMessage);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}

Result<DOMRangeAndAdjustedOffsetInFlattenedText, nsresult> result =
ConvertFlatTextOffsetToDOMRange(aOffset, aLength, LINE_BREAK_TYPE_NATIVE,
false);
if (NS_WARN_IF(result.isErr())) {
return nullptr;
}

DOMRangeAndAdjustedOffsetInFlattenedText domRangeAndAdjustOffset =
result.unwrap();

return nsRange::Create(domRangeAndAdjustOffset.mRange.Start(),
domRangeAndAdjustOffset.mRange.End(), IgnoreErrors());
}

template <typename RangeType, typename TextNodeType>
Result<ContentEventHandler::DOMRangeAndAdjustedOffsetInFlattenedTextBase<
RangeType, TextNodeType>,
Expand Down
8 changes: 8 additions & 0 deletions dom/events/ContentEventHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,14 @@ class MOZ_STACK_CLASS ContentEventHandler {

static uint32_t GetNativeTextLength(const nsAString& aText);

// Get the range between start offset and end offset
MOZ_CAN_RUN_SCRIPT
already_AddRefed<nsRange> GetRangeFromFlatTextOffset(
WidgetContentCommandEvent* aEvent, uint32_t aOffset, uint32_t aLength);

// Get the contents of aRange as plain text.
nsresult GenerateFlatTextContent(const nsRange* aRange, nsString& aString);

protected:
// Get the text length of aTextNode.
static uint32_t GetTextLength(const dom::Text& aTextNode,
Expand Down
82 changes: 82 additions & 0 deletions dom/events/EventStateManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,9 @@ nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
case eContentCommandInsertText:
DoContentCommandInsertTextEvent(aEvent->AsContentCommandEvent());
break;
case eContentCommandReplaceText:
DoContentCommandReplaceTextEvent(aEvent->AsContentCommandEvent());
break;
case eContentCommandScroll:
DoContentCommandScrollEvent(aEvent->AsContentCommandEvent());
break;
Expand Down Expand Up @@ -6847,6 +6850,85 @@ nsresult EventStateManager::DoContentCommandInsertTextEvent(
return NS_OK;
}

nsresult EventStateManager::DoContentCommandReplaceTextEvent(
WidgetContentCommandEvent* aEvent) {
MOZ_ASSERT(aEvent);
MOZ_ASSERT(aEvent->mMessage == eContentCommandReplaceText);
MOZ_DIAGNOSTIC_ASSERT(aEvent->mString.isSome());
MOZ_DIAGNOSTIC_ASSERT(!aEvent->mString.ref().IsEmpty());

aEvent->mIsEnabled = false;
aEvent->mSucceeded = false;

NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);

if (XRE_IsParentProcess()) {
// Handle it in focused content process if there is.
if (BrowserParent* remote = BrowserParent::GetFocused()) {
Unused << remote->SendReplaceText(
aEvent->mSelection.mReplaceSrcString, aEvent->mString.ref(),
aEvent->mSelection.mOffset, aEvent->mSelection.mPreventSetSelection);
aEvent->mIsEnabled = true; // XXX it can be a lie...
aEvent->mSucceeded = true;
return NS_OK;
}
}

// If there is no active editor in this process, we should treat the command
// is disabled.
RefPtr<EditorBase> activeEditor =
nsContentUtils::GetActiveEditor(mPresContext);
if (NS_WARN_IF(!activeEditor)) {
aEvent->mSucceeded = true;
return NS_OK;
}

RefPtr<TextComposition> composition =
IMEStateManager::GetTextCompositionFor(mPresContext);
if (NS_WARN_IF(composition)) {
// We don't support replace text action during composition.
aEvent->mSucceeded = true;
return NS_OK;
}

ContentEventHandler handler(mPresContext);
RefPtr<nsRange> range = handler.GetRangeFromFlatTextOffset(
aEvent, aEvent->mSelection.mOffset,
aEvent->mSelection.mReplaceSrcString.Length());
if (NS_WARN_IF(!range)) {
aEvent->mSucceeded = false;
return NS_OK;
}

// If original replacement text isn't matched with selection text, throws
// error.
nsAutoString targetStr;
nsresult rv = handler.GenerateFlatTextContent(range, targetStr);
if (NS_WARN_IF(NS_FAILED(rv))) {
aEvent->mSucceeded = false;
return NS_OK;
}
if (!aEvent->mSelection.mReplaceSrcString.Equals(targetStr)) {
aEvent->mSucceeded = false;
return NS_OK;
}

rv = activeEditor->ReplaceTextAsAction(
aEvent->mString.ref(), range,
TextEditor::AllowBeforeInputEventCancelable::Yes,
aEvent->mSelection.mPreventSetSelection
? EditorBase::PreventSetSelection::Yes
: EditorBase::PreventSetSelection::No);
if (NS_WARN_IF(NS_FAILED(rv))) {
aEvent->mSucceeded = false;
return NS_OK;
}

aEvent->mIsEnabled = rv != NS_SUCCESS_DOM_NO_OPERATION;
aEvent->mSucceeded = true;
return NS_OK;
}

nsresult EventStateManager::DoContentCommandScrollEvent(
WidgetContentCommandEvent* aEvent) {
NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
Expand Down
2 changes: 2 additions & 0 deletions dom/events/EventStateManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,8 @@ class EventStateManager : public nsSupportsWeakReference, public nsIObserver {
nsresult DoContentCommandEvent(WidgetContentCommandEvent* aEvent);
MOZ_CAN_RUN_SCRIPT
nsresult DoContentCommandInsertTextEvent(WidgetContentCommandEvent* aEvent);
MOZ_CAN_RUN_SCRIPT
nsresult DoContentCommandReplaceTextEvent(WidgetContentCommandEvent* aEvent);
nsresult DoContentCommandScrollEvent(WidgetContentCommandEvent* aEvent);

dom::BrowserParent* GetCrossProcessTarget();
Expand Down
3 changes: 1 addition & 2 deletions dom/html/TextControlState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2809,8 +2809,7 @@ bool TextControlState::SetValueWithTextEditor(
aHandlingSetValue.GetSettingValue(), nullptr,
StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
? TextEditor::AllowBeforeInputEventCancelable::Yes
: TextEditor::AllowBeforeInputEventCancelable::No,
nullptr);
: TextEditor::AllowBeforeInputEventCancelable::No);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::ReplaceTextAsAction() failed");
return rv != NS_ERROR_OUT_OF_MEMORY;
Expand Down
22 changes: 18 additions & 4 deletions dom/interfaces/base/nsIDOMWindowUtils.idl
Original file line number Diff line number Diff line change
Expand Up @@ -1247,23 +1247,37 @@ interface nsIDOMWindowUtils : nsISupports {
*/
[implicit_jscontext] string getClassName(in jsval aObject);

/**
* If sendContentCommanedEvent()'s aAdditionalFlags argument has no
* CONTENT_COMMAND_FLAG_PREVENT_SET_SELECTION, after executing replaceText,
* selection is next of replaced text. If set this with aReplaceSrcString,
* we keeps selection position if selection isn't into replaced text.
*/
const unsigned long CONTENT_COMMAND_FLAG_PREVENT_SET_SELECTION = 0x0002;

/**
* Generate a content command event.
*
* Cannot be accessed from unprivileged context (not content-accessible)
* Will throw a DOM security error if called without chrome privileges.
*
* @param aType Type of command content event to send. Can be one of "cut",
* "copy", "paste", "delete", "undo", "redo", "insertText" or
* "pasteTransferable".
* "copy", "paste", "delete", "undo", "redo", "insertText",
* "pasteTransferable", or "replaceText"
* @param aTransferable an instance of nsITransferable when aType is
* "pasteTransferable"
* @param aString The string to be inserted into focused editor when aType is
* "insertText"
* "insertText" or "replaceText"
* @Param aOffset The relative to start of selection
* @param aReplaceSrcString the source string of replaceText. If not matched, do nothing.
* @param aAdditionalFlags See the description of CONTENT_COMMAND_FLAG_*.
*/
void sendContentCommandEvent(in AString aType,
[optional] in nsITransferable aTransferable,
[optional] in AString aString);
[optional] in AString aString,
[optional] in uint32_t aOffset,
[optional] in AString aReplaceSrcString,
[optional] in unsigned long aAdditionalFlags);

/**
* If sendQueryContentEvent()'s aAdditionalFlags argument is
Expand Down
21 changes: 21 additions & 0 deletions dom/ipc/BrowserChild.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2259,6 +2259,27 @@ mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityInsertText(
return RecvInsertText(aStringToInsert);
}

mozilla::ipc::IPCResult BrowserChild::RecvReplaceText(
const nsString& aReplaceSrcString, const nsString& aStringToInsert,
uint32_t aOffset, bool aPreventSetSelection) {
// Use normal event path to reach focused document.
WidgetContentCommandEvent localEvent(true, eContentCommandReplaceText,
mPuppetWidget);
localEvent.mString = Some(aStringToInsert);
localEvent.mSelection.mReplaceSrcString = aReplaceSrcString;
localEvent.mSelection.mOffset = aOffset;
localEvent.mSelection.mPreventSetSelection = aPreventSetSelection;
DispatchWidgetEventViaAPZ(localEvent);
return IPC_OK();
}

mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityReplaceText(
const nsString& aReplaceSrcString, const nsString& aStringToInsert,
uint32_t aOffset, bool aPreventSetSelection) {
return RecvReplaceText(aReplaceSrcString, aStringToInsert, aOffset,
aPreventSetSelection);
}

mozilla::ipc::IPCResult BrowserChild::RecvPasteTransferable(
const IPCTransferable& aTransferable) {
nsresult rv;
Expand Down
9 changes: 9 additions & 0 deletions dom/ipc/BrowserChild.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,15 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
mozilla::ipc::IPCResult RecvNormalPriorityInsertText(
const nsAString& aStringToInsert);

mozilla::ipc::IPCResult RecvReplaceText(const nsString& aReplaceSrcString,
const nsString& aStringToInsert,
uint32_t aOffset,
bool aPreventSetSelection);

mozilla::ipc::IPCResult RecvNormalPriorityReplaceText(
const nsString& aReplaceSrcString, const nsString& aStringToInsert,
uint32_t aOffset, bool aPreventSetSelection);

MOZ_CAN_RUN_SCRIPT_BOUNDARY
mozilla::ipc::IPCResult RecvPasteTransferable(
const IPCTransferable& aTransferable);
Expand Down
12 changes: 12 additions & 0 deletions dom/ipc/PBrowser.ipdl
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,18 @@ child:
[Priority=input] async InsertText(nsString aStringToInsert);
async NormalPriorityInsertText(nsString aStringToInsert);

/**
* Dispatch eContentCommandReplaceText event in the remote process.
*/
[Priority=input] async ReplaceText(nsString aReplacementString,
nsString aStringToInsert,
uint32_t aOffset,
bool aPreventSetSelection);
async NormalPriorityReplaceText(nsString aReplacementString,
nsString aStringToInsert,
uint32_t aOffset,
bool aPreventSetSelection);

/**
* Call PasteTransferable via a controller on the content process
* to handle the command content event, "pasteTransferable".
Expand Down
40 changes: 40 additions & 0 deletions editor/libeditor/AutoSelectionRestorer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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 "AutoSelectionRestorer.h"

namespace mozilla {

AutoSelectionRestorer::AutoSelectionRestorer(EditorBase* aEditor) {
if (!aEditor) {
return;
}
if (aEditor->ArePreservingSelection()) {
// We already have initialized mParentData::mSavedSelection, so this must
// be nested call.
return;
}
MOZ_ASSERT(aEditor->IsEditActionDataAvailable());
mEditor = aEditor;
mEditor->PreserveSelectionAcrossActions();
}

AutoSelectionRestorer::~AutoSelectionRestorer() {
if (!mEditor || !mEditor->ArePreservingSelection()) {
return;
}
DebugOnly<nsresult> rvIgnored = mEditor->RestorePreservedSelection();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"EditorBase::RestorePreservedSelection() failed, but ignored");
}

void AutoSelectionRestorer::Abort() {
if (mEditor) {
mEditor->StopPreservingSelection();
}
}

} // namespace mozilla
Loading

0 comments on commit 131eaf7

Please sign in to comment.