/* -*- 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"EditorBase.h"
#include <stdio.h> // for nullptr, stdout #include <string.h> // for strcmp
#include"AutoClonedRangeArray.h"// for AutoClonedRangeArray and AutoClonedSelectionRangeArray #include"AutoSelectionRestorer.h" #include"ChangeAttributeTransaction.h" #include"CompositionTransaction.h" #include"DeleteContentTransactionBase.h" #include"DeleteMultipleRangesTransaction.h" #include"DeleteNodeTransaction.h" #include"DeleteRangeTransaction.h" #include"DeleteTextTransaction.h" #include"EditAction.h"// for EditSubAction #include"EditorDOMPoint.h"// for EditorDOMPoint #include"EditorForwards.h" #include"EditorUtils.h"// for various helper classes. #include"EditTransactionBase.h"// for EditTransactionBase #include"EditorEventListener.h"// for EditorEventListener #include"HTMLEditor.h"// for HTMLEditor #include"HTMLEditorInlines.h" #include"HTMLEditUtils.h"// for HTMLEditUtils #include"InsertNodeTransaction.h"// for InsertNodeTransaction #include"InsertTextTransaction.h"// for InsertTextTransaction #include"JoinNodesTransaction.h"// for JoinNodesTransaction #include"PlaceholderTransaction.h"// for PlaceholderTransaction #include"SplitNodeTransaction.h"// for SplitNodeTransaction #include"TextEditor.h"// for TextEditor
#include"ErrorList.h" #include"gfxFontUtils.h"// for gfxFontUtils #include"mozilla/Assertions.h" #include"mozilla/AsyncEventDispatcher.h" #include"mozilla/EditorDOMPoint.h" #include"mozilla/intl/BidiEmbeddingLevel.h" #include"mozilla/BasePrincipal.h"// for BasePrincipal #include"mozilla/CheckedInt.h"// for CheckedInt #include"mozilla/ComposerCommandsUpdater.h"// for ComposerCommandsUpdater #include"mozilla/ContentEvents.h"// for InternalClipboardEvent #include"mozilla/DebugOnly.h"// for DebugOnly #include"mozilla/EditorSpellCheck.h"// for EditorSpellCheck #include"mozilla/Encoding.h"// for Encoding (used in Document::GetDocumentCharacterSet) #include"mozilla/EventDispatcher.h"// for EventChainPreVisitor, etc. #include"mozilla/FlushType.h"// for FlushType::Frames #include"mozilla/IMEContentObserver.h"// for IMEContentObserver #include"mozilla/IMEStateManager.h"// for IMEStateManager #include"mozilla/InputEventOptions.h"// for InputEventOptions #include"mozilla/IntegerRange.h"// for IntegerRange #include"mozilla/InternalMutationEvent.h"// for NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED #include"mozilla/mozalloc.h"// for operator new, etc. #include"mozilla/mozInlineSpellChecker.h"// for mozInlineSpellChecker #include"mozilla/mozSpellChecker.h"// for mozSpellChecker #include"mozilla/Preferences.h"// for Preferences #include"mozilla/PresShell.h"// for PresShell #include"mozilla/RangeBoundary.h"// for RawRangeBoundary, RangeBoundary #include"mozilla/ScopeExit.h"// for MakeScopeExit #include"mozilla/Services.h"// for GetObserverService #include"mozilla/StaticPrefs_bidi.h"// for StaticPrefs::bidi_* #include"mozilla/StaticPrefs_dom.h"// for StaticPrefs::dom_* #include"mozilla/StaticPrefs_editor.h"// for StaticPrefs::editor_* #include"mozilla/StaticPrefs_layout.h"// for StaticPrefs::layout_* #include"mozilla/TextComposition.h"// for TextComposition #include"mozilla/TextControlElement.h"// for TextControlElement #include"mozilla/TextInputListener.h"// for TextInputListener #include"mozilla/TextServicesDocument.h"// for TextServicesDocument #include"mozilla/TextEvents.h" #include"mozilla/TransactionManager.h"// for TransactionManager #include"mozilla/dom/AbstractRange.h"// for AbstractRange #include"mozilla/dom/Attr.h"// for Attr #include"mozilla/dom/BorrowedAttrInfo.h"// for BorrowedAttrInfo #include"mozilla/dom/BrowsingContext.h"// for BrowsingContext #include"mozilla/dom/CharacterData.h"// for CharacterData #include"mozilla/dom/ContentParent.h"// for ContentParent #include"mozilla/dom/DataTransfer.h"// for DataTransfer #include"mozilla/dom/Document.h"// for Document #include"mozilla/dom/DocumentInlines.h"// for GetObservingPresShell #include"mozilla/dom/DragEvent.h"// for DragEvent #include"mozilla/dom/Element.h"// for Element, nsINode::AsElement #include"mozilla/dom/EventTarget.h"// for EventTarget #include"mozilla/dom/HTMLBodyElement.h" #include"mozilla/dom/HTMLBRElement.h" #include"mozilla/dom/Selection.h"// for Selection, etc. #include"mozilla/dom/StaticRange.h"// for StaticRange #include"mozilla/dom/Text.h" #include"mozilla/dom/Event.h" #include"nsAString.h"// for nsAString::Length, etc. #include"nsCCUncollectableMarker.h"// for nsCCUncollectableMarker #include"nsCaret.h"// for nsCaret #include"nsCaseTreatment.h" #include"nsCharTraits.h"// for NS_IS_HIGH_SURROGATE, etc. #include"nsContentUtils.h"// for nsContentUtils #include"nsCopySupport.h"// for nsCopySupport #include"nsDOMString.h"// for DOMStringIsNull #include"nsDebug.h"// for NS_WARNING, etc. #include"nsError.h"// for NS_OK, etc. #include"nsFocusManager.h"// for nsFocusManager #include"nsFrameSelection.h"// for nsFrameSelection #include"nsGenericHTMLElement.h"// for nsGenericHTMLElement #include"nsGkAtoms.h"// for nsGkAtoms, nsGkAtoms::dir #include"nsIClipboard.h"// for nsIClipboard #include"nsIContent.h"// for nsIContent #include"nsIContentInlines.h"// for nsINode::IsInDesignMode() #include"nsIDocumentEncoder.h"// for nsIDocumentEncoder #include"nsIDocumentStateListener.h"// for nsIDocumentStateListener #include"nsIDocShell.h"// for nsIDocShell #include"nsIEditActionListener.h"// for nsIEditActionListener #include"nsIFrame.h"// for nsIFrame #include"nsIInlineSpellChecker.h"// for nsIInlineSpellChecker, etc. #include"nsNameSpaceManager.h"// for kNameSpaceID_None, etc. #include"nsINode.h"// for nsINode, etc. #include"nsISelectionController.h"// for nsISelectionController, etc. #include"nsISelectionDisplay.h"// for nsISelectionDisplay, etc. #include"nsISupports.h"// for nsISupports #include"nsISupportsUtils.h"// for NS_ADDREF, NS_IF_ADDREF #include"nsITransferable.h"// for nsITransferable #include"nsIWeakReference.h"// for nsISupportsWeakReference #include"nsIWidget.h"// for nsIWidget, IMEState, etc. #include"nsPIDOMWindow.h"// for nsPIDOMWindow #include"nsPresContext.h"// for nsPresContext #include"nsRange.h"// for nsRange #include"nsReadableUtils.h"// for EmptyString, ToNewCString #include"nsString.h"// for nsAutoString, nsString, etc. #include"nsStringFwd.h"// for nsString #include"nsStyleConsts.h"// for StyleDirection::Rtl, etc. #include"nsStyleStruct.h"// for nsStyleDisplay, nsStyleText, etc. #include"nsStyleStructFwd.h"// for nsIFrame::StyleUIReset, etc. #include"nsTextNode.h"// for nsTextNode #include"nsThreadUtils.h"// for nsRunnable #include"prtime.h"// for PR_Now
class nsIOutputStream; class nsITransferable;
namespace mozilla {
usingnamespace dom; usingnamespace widget;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; using LeafNodeType = HTMLEditUtils::LeafNodeType; using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
EditorBase::~EditorBase() {
MOZ_ASSERT(!IsInitialized() || mDidPreDestroy, "Why PreDestroy hasn't been called?");
if (mComposition) {
mComposition->OnEditorDestroyed();
mComposition = nullptr;
} // If this editor is still hiding the caret, we need to restore it.
HideCaret(false);
mTransactionManager = nullptr;
}
NS_IMPL_CYCLE_COLLECTION_CLASS(EditorBase)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EditorBase) // Remove event listeners first since EditorEventListener may need // mDocument, mEventTarget, etc. if (tmp->mEventListener) {
tmp->mEventListener->Disconnect();
tmp->mEventListener = nullptr;
}
// First only set flags, but other stuff shouldn't be initialized now. // Note that SetFlags() will be called by PostCreate().
mFlags = aFlags;
mDocument = &aDocument; // nsISelectionController should be stored only when we're a `TextEditor`. // Otherwise, in `HTMLEditor`, it's `PresShell`, and grabbing it causes // a circular reference and memory leak. // XXX Should we move `mSelectionController to `TextEditor`?
MOZ_ASSERT_IF(!IsTextEditor(), &aSelectionController == GetPresShell()); if (IsTextEditor()) {
MOZ_ASSERT(&aSelectionController != GetPresShell());
mSelectionController = &aSelectionController;
}
if (mEditActionData) { // During edit action, selection is cached. But this selection is invalid // now since selection controller is updated, so we have to update this // cache.
Selection* selection = aSelectionController.GetSelection(
nsISelectionController::SELECTION_NORMAL);
NS_WARNING_ASSERTION(selection, "SelectionController::GetSelection() failed"); if (selection) {
mEditActionData->UpdateSelectionCache(*selection);
}
}
// set up root element if we are passed one. if (aRootElement) {
mRootElement = aRootElement;
}
// If this is an editor for <input> or <textarea>, the text node which // has composition string is always recreated with same content. Therefore, // we need to nodify mComposition of text node destruction and replacing // composing string when this receives eCompositionChange event next time. if (mComposition && mComposition->GetContainerTextNode() &&
!mComposition->GetContainerTextNode()->IsInComposedDoc()) {
mComposition->OnTextNodeRemoved();
}
// Show the caret.
DebugOnly<nsresult> rvIgnored = aSelectionController.SetCaretReadOnly(false);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "nsISelectionController::SetCaretReadOnly(false) failed, but ignored"); // Show all the selection reflected to user.
rvIgnored =
aSelectionController.SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsISelectionController::SetSelectionFlags(" "nsISelectionDisplay::DISPLAY_ALL) failed, but ignored");
// Make sure that the editor will be destroyed properly
mDidPreDestroy = false; // Make sure that the editor will be created properly
mDidPostCreate = false;
MOZ_ASSERT(IsBeingInitialized());
AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_FAILURE;
}
// Synchronize some stuff for the flags. SetFlags() will initialize // something by the flag difference. This is first time of that, so, all // initializations must be run. For such reason, we need to invert mFlags // value first.
mFlags = ~mFlags;
nsresult rv = SetFlags(~mFlags); if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::SetFlags() failed"); return EditorBase::ToGenericNSResult(rv);
}
// These operations only need to happen on the first PostCreate call if (!mDidPostCreate) {
mDidPostCreate = true;
// Set up listeners
CreateEventListeners();
nsresult rv = InstallEventListeners(); if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::InstallEventListeners() failed"); return EditorBase::ToGenericNSResult(rv);
}
// nuke the modification count, so the doc appears unmodified // do this before we notify listeners
DebugOnly<nsresult> rvIgnored = ResetModificationCount();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "EditorBase::ResetModificationCount() failed, but ignored");
// update the UI with our state
rvIgnored = NotifyDocumentListeners(eDocumentCreated);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "EditorBase::NotifyDocumentListeners(eDocumentCreated)" " failed, but ignored");
rvIgnored = NotifyDocumentListeners(eDocumentStateChanged);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "EditorBase::NotifyDocumentListeners(" "eDocumentStateChanged) failed, but ignored");
}
// update nsTextStateManager and caret if we have focus if (RefPtr<Element> focusedElement = GetFocusedElement()) {
DebugOnly<nsresult> rvIgnored = InitializeSelection(*focusedElement);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "EditorBase::InitializeSelection() failed, but ignored");
// If the text control gets reframed during focus, Focus() would not be // called, so take a chance here to see if we need to spell check the text // control.
nsresult rv = FlushPendingSpellCheck(); if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING( "EditorBase::FlushPendingSpellCheck() caused destroying the editor"); return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "EditorBase::FlushPendingSpellCheck() failed, but ignored");
void EditorBase::CreateEventListeners() { // Don't create the handler twice if (!mEventListener) {
mEventListener = new EditorEventListener();
}
}
nsresult EditorBase::InstallEventListeners() { // FIXME InstallEventListeners() should not be called if we failed to set // document or create an event listener. So, these checks should be // MOZ_DIAGNOSTIC_ASSERT instead.
MOZ_ASSERT(GetDocument()); if (MOZ_UNLIKELY(!GetDocument()) || NS_WARN_IF(!mEventListener)) { return NS_ERROR_NOT_INITIALIZED;
}
// Initialize the event target.
mEventTarget = GetExposedRoot(); if (NS_WARN_IF(!mEventTarget)) { return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv = mEventListener->Connect(this);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorEventListener::Connect() failed"); if (mComposition) { // If mComposition has already been destroyed, we should forget it. // This may happen if it ended while we don't listen to composition // events. if (mComposition->Destroyed()) { // XXX We may need to fix existing composition transaction here. // However, this may be called when it's not safe. // Perhaps, we should stop handling composition with events.
mComposition = nullptr;
} // Otherwise, Restart to handle composition with new editor contents. else {
mComposition->StartHandlingComposition(this);
}
} return rv;
}
void EditorBase::RemoveEventListeners() { if (!mEventListener) { return;
}
mEventListener->Disconnect(); if (mComposition) { // Even if this is called, don't release mComposition because this is // may be reused after reframing.
mComposition->EndHandlingComposition(this);
}
mEventTarget = nullptr;
}
bool EditorBase::GetDesiredSpellCheckState() { // Check user override on this element if (mSpellcheckCheckboxState != eTriUnset) { return (mSpellcheckCheckboxState == eTriTrue);
}
// Check user preferences
int32_t spellcheckLevel = StaticPrefs::layout_spellcheckDefault();
if (!spellcheckLevel) { returnfalse; // Spellchecking forced off globally
}
if (!CanEnableSpellCheck()) { returnfalse;
}
PresShell* presShell = GetPresShell(); if (presShell) {
nsPresContext* context = presShell->GetPresContext(); if (context && !context->IsDynamic()) { returnfalse;
}
}
// Check DOM state
nsCOMPtr<nsIContent> content = GetExposedRoot(); if (!content) { returnfalse;
}
auto element = nsGenericHTMLElement::FromNode(content); if (!element) { returnfalse;
}
// XXX I'm not sure whether we don't use this path when we're a plaintext mail // composer. if (IsHTMLEditor() && !AsHTMLEditor()->IsPlaintextMailComposer()) { // Some of the page content might be editable and some not, if spellcheck= // is explicitly set anywhere, so if there's anything editable on the page, // return true and let the spellchecker figure it out.
Document* doc = content->GetComposedDoc(); return doc && doc->IsEditingOn();
}
Selection* selection = GetSelection(); if (selection) {
selection->RemoveSelectionListener(this);
}
IMEStateManager::OnEditorDestroying(*this);
// Let spellchecker clean up its observers etc. It is important not to // actually free the spellchecker here, since the spellchecker could have // caused flush notifications, which could have gotten here if a textbox // is being removed. Setting the spellchecker to nullptr could free the // object that is still in use! It will be freed when the editor is // destroyed. if (mInlineSpellChecker) {
DebugOnly<nsresult> rvIgnored =
mInlineSpellChecker->Cleanup(IsTextEditor());
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "mozInlineSpellChecker::Cleanup() failed, but ignored");
}
// tell our listeners that the doc is going away
DebugOnly<nsresult> rvIgnored =
NotifyDocumentListeners(eDocumentToBeDestroyed);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "EditorBase::NotifyDocumentListeners(" "eDocumentToBeDestroyed) failed, but ignored");
// Unregister event listeners
RemoveEventListeners(); // If this editor is still hiding the caret, we need to restore it.
HideCaret(false);
mActionListeners.Clear();
mDocStateListeners.Clear();
mInlineSpellChecker = nullptr;
mTextServicesDocument = nullptr;
mTextInputListener = nullptr;
mSpellcheckCheckboxState = eTriUnset;
mRootElement = nullptr;
// Transaction may grab this instance. Therefore, they should be released // here for stopping the circular reference with this instance. if (mTransactionManager) {
DebugOnly<bool> disabledUndoRedo = DisableUndoRedo();
NS_WARNING_ASSERTION(disabledUndoRedo, "EditorBase::DisableUndoRedo() failed, but ignored");
mTransactionManager = nullptr;
}
if (mEditActionData) {
mEditActionData->OnEditorDestroy();
}
mDidPreDestroy = true;
}
NS_IMETHODIMP EditorBase::GetFlags(uint32_t* aFlags) { // NOTE: If you need to override this method, you need to make Flags() // virtual.
*aFlags = Flags(); return NS_OK;
}
// If we're a `TextEditor` instance, it's always a plaintext editor. // Therefore, `eEditorPlaintextMask` is not necessary and should not be set // for the performance reason.
MOZ_ASSERT_IF(IsTextEditor(), !(aFlags & nsIEditor::eEditorPlaintextMask)); // If we're an `HTMLEditor` instance, we cannot treat it as a single line // editor. So, eEditorSingleLineMask is available only when we're a // `TextEditor` instance.
MOZ_ASSERT_IF(IsHTMLEditor(), !(aFlags & nsIEditor::eEditorSingleLineMask)); // If we're an `HTMLEditor` instance, we cannot treat it as a password editor. // So, eEditorPasswordMask is available only when we're a `TextEditor` // instance.
MOZ_ASSERT_IF(IsHTMLEditor(), !(aFlags & nsIEditor::eEditorPasswordMask)); // eEditorAllowInteraction changes the behavior of `HTMLEditor`. So, it's // not available with `TextEditor` instance.
MOZ_ASSERT_IF(IsTextEditor(), !(aFlags & nsIEditor::eEditorAllowInteraction));
constbool isCalledByPostCreate = (mFlags == ~aFlags); // We don't support dynamic password flag change.
MOZ_ASSERT_IF(!isCalledByPostCreate,
!((mFlags ^ aFlags) & nsIEditor::eEditorPasswordMask)); bool spellcheckerWasEnabled = !isCalledByPostCreate && CanEnableSpellCheck();
mFlags = aFlags;
if (!IsInitialized()) { // If we're initializing, we shouldn't do anything now. // SetFlags() will be called by PostCreate(), // we should synchronize some stuff for the flags at that time. return NS_OK;
}
// The flag change may cause the spellchecker state change if (CanEnableSpellCheck() != spellcheckerWasEnabled) {
SyncRealTimeSpell();
}
// If this is called from PostCreate(), it will update the IME state if it's // necessary. if (!mDidPostCreate) { return NS_OK;
}
// Might be changing editable state, so, we need to reset current IME state // if we're focused and the flag change causes IME state change. if (RefPtr<Element> focusedElement = GetFocusedElement()) {
IMEState newState;
nsresult rv = GetPreferredIMEState(&newState);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "EditorBase::GetPreferredIMEState() failed, but ignored"); if (NS_SUCCEEDED(rv)) { // NOTE: When the enabled state isn't going to be modified, this method // is going to do nothing.
IMEStateManager::UpdateIMEState(newState, focusedElement, *this);
}
}
if (IsTextEditor()) { // XXX we just check that the anchor node is editable at the moment // we should check that all nodes in the selection are editable const nsINode* anchorNode = SelectionRef().GetAnchorNode(); return anchorNode && anchorNode->IsContent() && anchorNode->IsEditable();
}
// if anchorNode or focusNode is in a native anonymous subtree, HTMLEditor // shouldn't edit content in it. // XXX This must be a bug of Selection API. if (MOZ_UNLIKELY(anchorNode->IsInNativeAnonymousSubtree() ||
focusNode->IsInNativeAnonymousSubtree())) { returnfalse;
}
// Per the editing spec as of June 2012: we have to have a selection whose // start and end nodes are editable, and which share an ancestor editing // host. (Bug 766387.) bool isSelectionEditable = SelectionRef().RangeCount() &&
anchorNode->IsEditable() &&
focusNode->IsEditable(); if (!isSelectionEditable) { returnfalse;
}
const nsINode* commonAncestor =
SelectionRef().GetAnchorFocusRange()->GetClosestCommonInclusiveAncestor(); while (commonAncestor && !commonAncestor->IsEditable()) {
commonAncestor = commonAncestor->GetParentNode();
} // If there is no editable common ancestor, return false. return !!commonAncestor;
}
nsresult EditorBase::DoTransactionInternal(nsITransaction* aTransaction) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT_IF( // If the DOM is modified by a clipboard event handler, // HTMLEditor::OnModifyDocument() may need to do some transactions before // dispatching `beforeinput`. // FIXME: It shouldn't happen, and I think that it should be done once // before dispatching `input` event to hide the our editor hack from // the event listeners.
GetEditAction() != EditAction::ePaste &&
GetEditAction() != EditAction::eCut,
!ShouldAlreadyHaveHandledBeforeInputEventDispatching());
// We will recurse, but will not hit this case in the nested call
RefPtr<PlaceholderTransaction> placeholderTransaction =
mPlaceholderTransaction;
DebugOnly<nsresult> rvIgnored =
DoTransactionInternal(placeholderTransaction);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "EditorBase::DoTransactionInternal() failed, but ignored");
if (mTransactionManager) { if (nsCOMPtr<nsITransaction> topTransaction =
mTransactionManager->PeekUndoStack()) { if (RefPtr<EditTransactionBase> topTransactionBase =
topTransaction->GetAsEditTransactionBase()) { if (PlaceholderTransaction* topPlaceholderTransaction =
topTransactionBase->GetAsPlaceholderTransaction()) { // there is a placeholder transaction on top of the undo stack. It // is either the one we just created, or an earlier one that we are // now merging into. From here on out remember this placeholder // instead of the one we just created.
mPlaceholderTransaction = topPlaceholderTransaction;
}
}
}
}
}
if (aTransaction) { // XXX: Why are we doing selection specific batching stuff here? // XXX: Most entry points into the editor have auto variables that // XXX: should trigger Begin/EndUpdateViewBatch() calls that will make // XXX: these selection batch calls no-ops. // XXX: // XXX: I suspect that this was placed here to avoid multiple // XXX: selection changed notifications from happening until after // XXX: the transaction was done. I suppose that can still happen // XXX: if an embedding application called DoTransaction() directly // XXX: to pump its own transactions through the system, but in that // XXX: case, wouldn't we want to use Begin/EndUpdateViewBatch() or // XXX: its auto equivalent AutoUpdateViewBatch to ensure that // XXX: selection listeners have access to accurate frame data? // XXX: // XXX: Note that if we did add Begin/EndUpdateViewBatch() calls // XXX: we will need to make sure that they are disabled during // XXX: the init of the editor for text widgets to avoid layout // XXX: re-entry during initial reflow. - kin
// get the selection and start a batch change
SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
// If we don't have transaction in the undo stack, we shouldn't notify // anybody of trying to undo since it's not useful notification but we // need to pay some runtime cost. if (!CanUndo()) { return NS_OK;
}
// If there is composition, we shouldn't allow to undo with committing // composition since Chrome doesn't allow it and it doesn't make sense // because committing composition causes one transaction and Undo(1) // undoes the committing composition. if (GetComposition()) { return NS_OK;
}
// If we don't have transaction in the redo stack, we shouldn't notify // anybody of trying to redo since it's not useful notification but we // need to pay some runtime cost. if (!CanRedo()) { return NS_OK;
}
// If there is composition, we shouldn't allow to redo with committing // composition since Chrome doesn't allow it and it doesn't make sense // because committing composition causes removing all transactions from // the redo queue. So, it becomes impossible to redo anything. if (GetComposition()) { return NS_OK;
}
if (!mPlaceholderBatch) {
NotifyEditorObservers(eNotifyEditorObserversOfBefore); // time to turn on the batch
BeginUpdateViewBatch(aRequesterFuncName);
mPlaceholderTransaction = nullptr;
mPlaceholderName = &aTransactionName;
mSelState.emplace();
mSelState->SaveSelection(SelectionRef()); // Composition transaction can modify multiple nodes and it merges text // node for ime into single text node. // So if current selection is into IME text node, it might be failed // to restore selection by UndoTransaction. // So we need update selection by range updater. if (mPlaceholderName == nsGkAtoms::IMETxnName) {
RangeUpdaterRef().RegisterSelectionState(*mSelState);
}
}
mPlaceholderBatch++;
}
void EditorBase::EndPlaceholderTransaction(
ScrollSelectionIntoView aScrollSelectionIntoView, constchar* aRequesterFuncName) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(mPlaceholderBatch > 0, "zero or negative placeholder batch count when ending batch!");
if (!(--mPlaceholderBatch)) { // By making the assumption that no reflow happens during the calls // to EndUpdateViewBatch and ScrollSelectionFocusIntoView, we are able to // allow the selection to cache a frame offset which is used by the // caret drawing code. We only enable this cache here; at other times, // we have no way to know whether reflow invalidates it // See bugs 35296 and 199412.
SelectionRef().SetCanCacheFrameOffset(true);
// time to turn off the batch
EndUpdateViewBatch(aRequesterFuncName); // make sure selection is in view
// After ScrollSelectionFocusIntoView(), the pending notifications might be // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. // XXX Even if we're destroyed, we need to keep handling below because // this method changes a lot of status. We should rewrite this safer. if (aScrollSelectionIntoView == ScrollSelectionIntoView::Yes) {
DebugOnly<nsresult> rvIgnored = ScrollSelectionFocusIntoView();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "EditorBase::ScrollSelectionFocusIntoView() failed, but Ignored");
}
// cached for frame offset are Not available now
SelectionRef().SetCanCacheFrameOffset(false);
if (mSelState) { // we saved the selection state, but never got to hand it to placeholder // (else we ould have nulled out this pointer), so destroy it to prevent // leaks. if (mPlaceholderName == nsGkAtoms::IMETxnName) {
RangeUpdaterRef().DropSelectionState(*mSelState);
}
mSelState.reset();
} // We might have never made a placeholder if no action took place. if (mPlaceholderTransaction) { // FYI: Disconnect placeholder transaction before dispatching "input" // event because an input event listener may start other things. // TODO: We should forget EditActionDataSetter too.
RefPtr<PlaceholderTransaction> placeholderTransaction =
std::move(mPlaceholderTransaction);
DebugOnly<nsresult> rvIgnored =
placeholderTransaction->EndPlaceHolderBatch();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "PlaceholderTransaction::EndPlaceHolderBatch() failed, but ignored"); // notify editor observers of action but if composing, it's done by // compositionchange event handler. if (!mComposition) {
NotifyEditorObservers(eNotifyEditorObserversOfEnd);
}
} else {
NotifyEditorObservers(eNotifyEditorObserversOfCancel);
}
}
}
// XXX: The rule system should tell us which node to select all on (ie, the // root, or the body)
NS_IMETHODIMP EditorBase::SelectAll() {
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_NOT_INITIALIZED;
}
nsresult rv = SelectAllInternal();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SelectAllInternal() failed"); // This is low level API for XUL applcation. So, we should return raw // error code here. return rv;
}
DebugOnly<nsresult> rvIgnored = CommitComposition(); if (NS_WARN_IF(Destroyed())) { return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "EditorBase::CommitComposition() failed, but ignored");
// XXX Do we need to keep handling after committing composition causes moving // focus to different element? Although TextEditor has independent // selection, so, we may not see any odd behavior even in such case.
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_NOT_INITIALIZED;
}
// get the root element
RefPtr<Element> rootElement = GetRoot(); if (NS_WARN_IF(!rootElement)) { return NS_ERROR_NULL_POINTER;
}
// find first editable thingy
nsCOMPtr<nsIContent> firstEditableLeaf; // If we're `TextEditor`, the first editable leaf node is a text node or // padding `<br>` element. In the first case, we need to collapse selection // into it. if (rootElement->GetFirstChild() && rootElement->GetFirstChild()->IsText()) {
firstEditableLeaf = rootElement->GetFirstChild();
} if (!firstEditableLeaf) { // just the root node, set selection to inside the root
nsresult rv = CollapseSelectionToStartOf(*rootElement);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionToStartOf() failed"); return rv;
}
if (firstEditableLeaf->IsText()) { // If firstEditableLeaf is text, set selection to beginning of the text // node.
nsresult rv = CollapseSelectionToStartOf(*firstEditableLeaf);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionToStartOf() failed"); return rv;
}
// Otherwise, it's a leaf node and we set the selection just in front of it.
nsCOMPtr<nsIContent> parent = firstEditableLeaf->GetParent(); if (NS_WARN_IF(!parent)) { return NS_ERROR_NULL_POINTER;
}
MOZ_ASSERT(
parent->ComputeIndexOf(firstEditableLeaf).valueOr(UINT32_MAX) == 0, "How come the first node isn't the left most child in its parent?");
nsresult rv = CollapseSelectionToStartOf(*parent);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionToStartOf() failed"); return rv;
}
nsresult rv =
ComputeValueInternal(aFormatType, aDocumentEncoderFlags, aOutputString);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::ComputeValueInternal() failed"); // This is low level API for XUL application. So, we should return raw // error code here. return rv;
}
// First, let's try to get the value simply only from text node if the // caller wants plaintext value. if (aFormatType.LowerCaseEqualsLiteral("text/plain") &&
!(aDocumentEncoderFlags & (nsIDocumentEncoder::OutputSelectionOnly |
nsIDocumentEncoder::OutputWrap))) { // Shortcut for empty editor case. if (IsEmpty()) {
aOutputString.Truncate(); return NS_OK;
} // NOTE: If it's neither <input type="text"> nor <textarea>, e.g., an HTML // editor which is in plaintext mode (e.g., plaintext email composer on // Thunderbird), it should be handled by the expensive path. if (IsTextEditor()) { // If it's necessary to check selection range or the editor wraps hard, // we need some complicated handling. In such case, we need to use the // expensive path. // XXX Anything else what we cannot return the text node data simply?
Result<EditActionResult, nsresult> result =
AsTextEditor()->ComputeValueFromTextNodeAndBRElement(aOutputString); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("TextEditor::ComputeValueFromTextNodeAndBRElement() failed"); return result.unwrapErr();
} if (!result.inspect().Ignored()) { return NS_OK;
}
}
}
nsAutoCString charset;
nsresult rv = GetDocumentCharsetInternal(charset); if (NS_FAILED(rv) || charset.IsEmpty()) {
charset.AssignLiteral("windows-1252"); // XXX Why don't we use "UTF-8"?
}
// Set the selection, if appropriate. // We do this either if the OutputSelectionOnly flag is set, // in which case we use our existing selection ... if (aDocumentEncoderFlags & nsIDocumentEncoder::OutputSelectionOnly) { if (NS_FAILED(docEncoder->SetSelection(&SelectionRef()))) {
NS_WARNING("nsIDocumentEncoder::SetSelection() failed"); return nullptr;
}
} // ... or if the root element is not a body, // in which case we set the selection to encompass the root. else {
Element* rootElement = GetRoot(); if (NS_WARN_IF(!rootElement)) { return nullptr;
} if (!rootElement->IsHTMLElement(nsGkAtoms::body)) { if (NS_FAILED(docEncoder->SetContainerNode(rootElement))) {
NS_WARNING("nsIDocumentEncoder::SetContainerNode() failed"); return nullptr;
}
}
}
// 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()) { returnfalse;
}
// 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> presShell = document->GetObservingPresShell(); if (!presShell) { returnfalse;
}
RefPtr<nsPresContext> presContext = presShell->GetPresContext(); if (!presContext) { returnfalse;
}
RefPtr<EventTarget> et = IsHTMLEditor()
? AsHTMLEditor()->ComputeEditingHost(
HTMLEditor::LimitInBodyElement::No)
: GetDOMEventTarget();
while (et) {
EventListenerManager* elm = et->GetExistingListenerManager(); if (elm && elm->HasListenersFor(aCommand)) { returntrue;
}
InternalClipboardEvent event(true, aEventMessage);
EventChainPreVisitor visitor(presContext, &event, nullptr,
nsEventStatus_eIgnore, false, et);
et->GetEventTargetParent(visitor);
et = visitor.GetParentTarget();
}
// Clipboard events are fired before `beforeinput` event. Therefore, we // need to forget mLastCollapsibleWhiteSpaceAppendedTextNode here to avoid // infinite loop caused by the hack. if (IsHTMLEditor()) {
AsHTMLEditor()->mLastCollapsibleWhiteSpaceAppendedTextNode = nullptr;
}
RefPtr<PresShell> presShell = GetPresShell(); if (NS_WARN_IF(!presShell)) { return Err(NS_ERROR_NOT_AVAILABLE);
}
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());
}();
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 (isPasting) { 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;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.