/* -*- 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/. */
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; using LeafNodeType = HTMLEditUtils::LeafNodeType; using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
// Some utilities to handle overloading of "A" tag for link and named anchor. staticbool IsLinkTag(const nsAtom& aTagName) { return &aTagName == nsGkAtoms::href;
}
nsresult HTMLEditor::Init(Document& aDocument,
ComposerCommandsUpdater& aComposerCommandsUpdater,
uint32_t aFlags) {
MOZ_ASSERT(!mInitSucceeded, "HTMLEditor::Init() called again without calling PreDestroy()?");
// init the type-in state
mPendingStylesToApplyToNewContent = new PendingStyles();
AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_FAILURE;
}
rv = InitEditorContentAndSelection(); if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::InitEditorContentAndSelection() failed"); // XXX Sholdn't we expose `NS_ERROR_EDITOR_DESTROYED` even though this // is a public method? return EditorBase::ToGenericNSResult(rv);
}
// Throw away the old transaction manager if this is not the first time that // we're initializing the editor.
ClearUndoRedo();
EnableUndoRedo(); // FYI: Creating mTransactionManager in this call
if (mTransactionManager) {
mTransactionManager->Attach(*this);
}
void HTMLEditor::PreDestroy() { if (mDidPreDestroy) { return;
}
mInitSucceeded = false;
// FYI: Cannot create AutoEditActionDataSetter here. However, it does not // necessary for the methods called by the following code.
RefPtr<Document> document = GetDocument(); if (document) {
document->RemoveMutationObserver(this);
}
// Clean up after our anonymous content -- we don't want these nodes to // stay around (which they would, since the frames have an owning reference).
PresShell* presShell = GetPresShell(); if (presShell && presShell->IsDestroying()) { // Just destroying PresShell now. // We have to keep UI elements of anonymous content until PresShell // is destroyed.
RefPtr<HTMLEditor> self = this;
nsContentUtils::AddScriptRunner(
NS_NewRunnableFunction("HTMLEditor::PreDestroy",
[self]() { self->HideAnonymousEditingUIs(); }));
} else { // PresShell is alive or already gone.
HideAnonymousEditingUIs();
}
RefPtr<Document> document = GetDocument(); if (NS_WARN_IF(!document)) { return EditorBase::ToGenericNSResult(NS_ERROR_NOT_INITIALIZED);
} // This method is scriptable, so add-ons could pass in something other // than a canonical name. const Encoding* encoding = Encoding::ForLabelNoReplacement(aCharacterSet); if (!encoding) {
NS_WARNING("Encoding::ForLabelNoReplacement() failed"); return EditorBase::ToGenericNSResult(NS_ERROR_INVALID_ARG);
}
document->SetDocumentCharacterSet(WrapNotNull(encoding));
// Update META charset element. if (UpdateMetaCharsetWithTransaction(*document, aCharacterSet)) { return NS_OK;
}
// Set attributes to the created element if (aCharacterSet.IsEmpty()) { return NS_OK;
}
RefPtr<nsContentList> headElementList =
document->GetElementsByTagName(u"head"_ns); if (NS_WARN_IF(!headElementList)) { return NS_OK;
}
nsCOMPtr<nsIContent> primaryHeadElement = headElementList->Item(0); if (NS_WARN_IF(!primaryHeadElement)) { return NS_OK;
}
// Create a new meta charset tag
Result<CreateElementResult, nsresult> createNewMetaElementResult =
CreateAndInsertElement(
WithTransaction::Yes, *nsGkAtoms::meta,
EditorDOMPoint(primaryHeadElement, 0),
[&aCharacterSet](HTMLEditor&, Element& aMetaElement, const EditorDOMPoint&) {
MOZ_ASSERT(!aMetaElement.IsInComposedDoc());
DebugOnly<nsresult> rvIgnored =
aMetaElement.SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
u"Content-Type"_ns, false);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "Element::SetAttr(nsGkAtoms::httpEquiv, \"Content-Type\", " "false) failed, but ignored");
rvIgnored =
aMetaElement.SetAttr(kNameSpaceID_None, nsGkAtoms::content,
u"text/html;charset="_ns +
NS_ConvertASCIItoUTF16(aCharacterSet), false);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
nsPrintfCString( "Element::SetAttr(nsGkAtoms::content, " "\"text/html;charset=%s\", false) failed, but ignored",
nsPromiseFlatCString(aCharacterSet).get())
.get()); return NS_OK;
});
NS_WARNING_ASSERTION(createNewMetaElementResult.isOk(), "HTMLEditor::CreateAndInsertElement(WithTransaction::" "Yes, nsGkAtoms::meta) failed, but ignored"); // Probably, we don't need to update selection in this case since we should // not put selection into <head> element.
createNewMetaElementResult.inspect().IgnoreCaretPointSuggestion(); return NS_OK;
}
bool HTMLEditor::UpdateMetaCharsetWithTransaction(
Document& aDocument, const nsACString& aCharacterSet) { // get a list of META tags
RefPtr<nsContentList> metaElementList =
aDocument.GetElementsByTagName(u"meta"_ns); if (NS_WARN_IF(!metaElementList)) { returnfalse;
}
for (uint32_t i = 0; i < metaElementList->Length(true); ++i) {
RefPtr<Element> metaElement = metaElementList->Item(i)->AsElement();
MOZ_ASSERT(metaElement);
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_NOT_INITIALIZED;
}
if (mPendingStylesToApplyToNewContent) {
RefPtr<PendingStyles> pendingStyles = mPendingStylesToApplyToNewContent;
pendingStyles->OnSelectionChange(*this, aReason);
// We used a class which derived from nsISelectionListener to call // HTMLEditor::RefreshEditingUI(). The lifetime of the class was // exactly same as mPendingStylesToApplyToNewContent. So, call it only when // mPendingStylesToApplyToNewContent is not nullptr. if ((aReason & (nsISelectionListener::MOUSEDOWN_REASON |
nsISelectionListener::KEYPRESS_REASON |
nsISelectionListener::SELECTALL_REASON)) &&
aSelection) { // the selection changed and we need to check if we have to // hide and/or redisplay resizing handles // FYI: This is an XPCOM method. So, the caller, Selection, guarantees // the lifetime of this instance. So, don't need to grab this with // local variable.
DebugOnly<nsresult> rv = RefreshEditingUI();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "HTMLEditor::RefreshEditingUI() failed, but ignored");
}
}
if (mComposerCommandsUpdater) {
RefPtr<ComposerCommandsUpdater> updater = mComposerCommandsUpdater;
updater->OnSelectionChange();
}
void HTMLEditor::UpdateRootElement() { // Use the HTML documents body element as the editor root if we didn't // get a root element during initialization.
mRootElement = GetBodyElement(); if (!mRootElement) {
RefPtr<Document> doc = GetDocument(); if (doc) { // If there is no HTML body element, // we should use the document root element instead.
mRootElement = doc->GetDocumentElement();
} // else leave it null, for lack of anything better.
}
}
// If we should've already handled focus event, selection limiter should not // be set. However, IMEStateManager is not notified the pseudo focus change // in this case. Therefore, we need to notify IMEStateManager of this. if (mHasFocus) { if (enteringInDesignMode) {
mIsInDesignMode = true; return NS_OK;
} // Although editor is already initialized due to re-used, ISM may not // create IME content observer yet. So we have to create it.
IMEState newState;
nsresult rv = GetPreferredIMEState(&newState); if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::GetPreferredIMEState() failed");
mIsInDesignMode = false; return NS_OK;
} const RefPtr<Element> focusedElement = GetFocusedElement(); if (focusedElement) {
MOZ_ASSERT(focusedElement == aElement);
TextControlElement* const textControlElement =
TextControlElement::FromNode(focusedElement); if (textControlElement &&
textControlElement->IsSingleLineTextControlOrTextArea()) { // Let's emulate blur first.
DebugOnly<nsresult> rv = FinalizeSelection();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "HTMLEditor::FinalizeSelection() failed, but ignored");
mHasFocus = false;
mIsInDesignMode = false;
}
IMEStateManager::UpdateIMEState(newState, focusedElement, *this); // XXX Do we need to notify focused TextEditor of focus? In theory, // the TextEditor should get focus event in this case.
}
mIsInDesignMode = false; return NS_OK;
}
// If we should be in the design mode, we want to handle focus event fired // on the document node. Therefore, we should emulate it here. if (enteringInDesignMode) {
MOZ_ASSERT(&aDocument == GetDocument());
nsresult rv = OnFocus(aDocument);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnFocus() failed"); return rv;
}
if (NS_WARN_IF(!aElement)) { return NS_ERROR_INVALID_ARG;
}
// Otherwise, we should've already handled focus event on the element, // therefore, we need to emulate it here.
MOZ_ASSERT(nsFocusManager::GetFocusedElementStatic() == aElement);
nsresult rv = OnFocus(*aElement);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnFocus() failed");
// Note that we don't need to call // IMEStateManager::MaybeOnEditableStateDisabled here because // EditorBase::OnFocus must have already been called IMEStateManager::OnFocus // if succeeded. And perhaps, it's okay that IME is not enabled when // HTMLEditor fails to start handling since nobody can handle composition // events anyway...
// Before doing anything, we should check whether the original target is still // valid focus event target because it may have already lost focus. if (!CanKeepHandlingFocusEvent(aOriginalEventTargetNode)) { return NS_OK;
}
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_FAILURE;
}
nsresult rv = [&]() MOZ_CAN_RUN_SCRIPT { // If HTMLEditor has not been created yet, we just need to adjust // IMEStateManager. So, don't return error. if (!aHTMLEditor || !aHTMLEditor->HasFocus()) { return NS_OK;
}
RefPtr<Element> focusedElement = nsFocusManager::GetFocusedElementStatic(); if (focusedElement && !focusedElement->IsInComposedDoc()) { // nsFocusManager may keep storing the focused element even after // disconnected from the tree, but HTMLEditor cannot work with editable // nodes not in a composed document. Therefore, we should treat no // focused element in the case.
focusedElement = nullptr;
}
TextControlElement* const focusedTextControlElement =
TextControlElement::FromNodeOrNull(focusedElement); if ((focusedElement && focusedElement->IsEditable() &&
focusedElement->OwnerDoc() == aHTMLEditor->GetDocument() &&
(!focusedTextControlElement ||
!focusedTextControlElement->IsSingleLineTextControlOrTextArea())) ||
(!focusedElement && aDocument.IsInDesignMode())) { // Then, the focused element is still editable, let's emulate focus to // make the editor be ready to handle input.
DebugOnly<nsresult> rvIgnored = aHTMLEditor->OnFocus(
focusedElement ? static_cast<nsINode&>(*focusedElement)
: static_cast<nsINode&>(aDocument));
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "HTMLEditor::OnFocus() failed, but ignored");
} elseif (focusedTextControlElement &&
focusedTextControlElement->IsSingleLineTextControlOrTextArea()) { if (const RefPtr<TextEditor> textEditor =
focusedTextControlElement->GetExtantTextEditor()) {
textEditor->OnFocus(*focusedElement);
}
} return rv;
}();
// If the element becomes not editable without focus change, IMEStateManager // does not have a chance to disable IME. Therefore, (even if we fail to // handle the emulated blur/focus above,) we should notify IMEStateManager of // the editing state change. Note that if the window of the HTMLEditor has // already lost focus, we don't need to do that and we should not touch the // other windows. if (const RefPtr<nsPresContext> presContext = aDocument.GetPresContext()) { const RefPtr<Element> focusedElementInDocument =
Element::FromNodeOrNull(aDocument.GetUnretargetedFocusedContent());
MOZ_ASSERT_IF(focusedElementInDocument,
focusedElementInDocument->GetPresContext(
Element::PresContextFor::eForComposedDoc));
IMEStateManager::MaybeOnEditableStateDisabled(*presContext,
focusedElementInDocument);
}
// If another element already has focus, we should not maintain the selection // because we may not have the rights doing it. const Element* focusedElement = nsFocusManager::GetFocusedElementStatic(); if (focusedElement && focusedElement != eventTargetAsElement) { // XXX If we had focus and new focused element is a text control, we may // need to notify focus of its TextEditor...
mIsInDesignMode = false;
mHasFocus = false; return NS_OK;
}
// If we're in the designMode and blur occurs, the target must be the document // node. If a blur event is fired and the target is an element, it must be // delayed blur event at initializing the `HTMLEditor`. if (mIsInDesignMode && eventTargetAsElement &&
eventTargetAsElement->IsInComposedDoc()) { return NS_OK;
}
Element* HTMLEditor::FindSelectionRoot(const nsINode& aNode) const {
MOZ_ASSERT(aNode.IsDocument() || aNode.IsContent(), "aNode must be content or document node");
if (NS_WARN_IF(!aNode.IsInComposedDoc())) { return nullptr;
}
if (aNode.IsInDesignMode()) { return GetDocument()->GetRootElement();
}
nsIContent* content = const_cast<nsIContent*>(aNode.AsContent()); if (!content->HasFlag(NODE_IS_EDITABLE)) { // If the content is in read-write state but is not editable itself, // return it as the selection root. if (content->IsElement() &&
content->AsElement()->State().HasState(ElementState::READWRITE)) { return content->AsElement();
} return nullptr;
}
// For non-readonly editors we want to find the root of the editable subtree // containing aContent. return content->GetEditingHost();
}
void HTMLEditor::CreateEventListeners() { // Don't create the handler twice if (!mEventListener) {
mEventListener = new HTMLEditorEventListener();
}
}
nsresult HTMLEditor::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;
}
// NOTE: HTMLEditor doesn't need to initialize mEventTarget here because // the target must be document node and it must be referenced as weak pointer.
NS_IMETHODIMP HTMLEditor::EndOfDocument() {
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_NOT_INITIALIZED;
}
nsresult rv = CollapseSelectionToEndOfLastLeafNodeOfDocument();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() failed"); // This is low level API for embedders and chrome script so that we can return // raw error code here. return rv;
}
// Hack for initializing selection. // HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode() will try to // collapse selection at first editable text node or inline element which // cannot have text nodes as its children. However, selection has already // set into the new editing host by user, we should not change it. For // solving this issue, we should do nothing if selection range is in active // editing host except it's not collapsed at start of the editing host since // aSelection.SetAncestorLimiter(aAncestorLimit) will collapse selection // at start of the new limiter if focus node of aSelection is outside of the // editing host. However, we need to check here if selection is already // collapsed at start of the editing host because it's possible JS to do it. // In such case, we should not modify selection with calling // MaybeCollapseSelectionAtFirstEditableNode().
// Basically, we should try to collapse selection at first editable node // in HTMLEditor. bool tryToCollapseSelectionAtFirstEditableNode = true; if (SelectionRef().RangeCount() == 1 && SelectionRef().IsCollapsed()) {
Element* editingHost = ComputeEditingHost(); const nsRange* range = SelectionRef().GetRangeAt(0); if (range->GetStartContainer() == editingHost && !range->StartOffset()) { // JS or user operation has already collapsed selection at start of // the editing host. So, we don't need to try to change selection // in this case.
tryToCollapseSelectionAtFirstEditableNode = false;
}
}
// XXX Do we need to check if we still need to change selection? E.g., // we could have already lost focus while we're changing the ancestor // limiter because it may causes "selectionchange" event. if (tryToCollapseSelectionAtFirstEditableNode) {
DebugOnly<nsresult> rvIgnored =
MaybeCollapseSelectionAtFirstEditableNode(true);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(true) failed, " "but ignored");
}
// If the target is a text control element, we won't handle user input // for the `TextEditor` in it. However, we need to be open for `execCommand`. // Therefore, we shouldn't set ancestor limit in this case. // Note that we should do this once setting ancestor limiter for backward // compatiblity of select events, etc. (Selection should be collapsed into // the text control element.) if (aAncestorLimit.HasIndependentSelection()) {
SelectionRef().SetAncestorLimiter(nullptr);
}
}
RefPtr<Element> editingHost = ComputeEditingHost(LimitInBodyElement::No); if (NS_WARN_IF(!editingHost)) { return NS_OK;
}
// If selection range is already in the editing host and the range is not // start of the editing host, we shouldn't reset selection. E.g., window // is activated when the editor had focus before inactivated. if (aIgnoreIfSelectionInEditingHost && SelectionRef().RangeCount() == 1) { const nsRange* range = SelectionRef().GetRangeAt(0); if (!range->Collapsed() ||
range->GetStartContainer() != editingHost.get() ||
range->StartOffset()) { return NS_OK;
}
}
for (nsIContent* leafContent = HTMLEditUtils::GetFirstLeafContent(
*editingHost,
{LeafNodeType::LeafNodeOrNonEditableNode,
LeafNodeType::LeafNodeOrChildBlock},
BlockInlineCheck::UseComputedDisplayStyle, editingHost);
leafContent;) { // If we meet a non-editable node first, we should move caret to start // of the container block or editing host. if (!EditorUtils::IsEditableContent(*leafContent, EditorType::HTML)) {
MOZ_ASSERT(leafContent->GetParent());
MOZ_ASSERT(EditorUtils::IsEditableContent(*leafContent->GetParent(),
EditorType::HTML)); if (const Element* editableBlockElementOrInlineEditingHost =
HTMLEditUtils::GetAncestorElement(
*leafContent,
HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
BlockInlineCheck::UseComputedDisplayStyle)) {
nsresult rv = CollapseSelectionTo(
EditorDOMPoint(editableBlockElementOrInlineEditingHost, 0));
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed"); return rv;
}
NS_WARNING("Found leaf content did not have editable parent, why?"); return NS_ERROR_FAILURE;
}
// When we meet an empty inline element, we should look for a next sibling. // For example, if current editor is: // <div contenteditable><span></span><b><br></b></div> // then, we should put caret at the <br> element. So, let's check if found // node is an empty inline container element. if (Element* leafElement = Element::FromNode(leafContent)) { if (HTMLEditUtils::IsInlineContent(
*leafElement, BlockInlineCheck::UseComputedDisplayStyle) &&
!HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafElement) &&
HTMLEditUtils::CanNodeContain(*leafElement,
*nsGkAtoms::textTagName)) { // Chromium collapses selection to start of the editing host when this // is the last leaf content. So, we don't need special handling here.
leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
*leafElement,
{LeafNodeType::LeafNodeOrNonEditableNode,
LeafNodeType::LeafNodeOrChildBlock},
BlockInlineCheck::UseComputedDisplayStyle, editingHost); continue;
}
}
if (Text* text = leafContent->GetAsText()) { // If there is editable and visible text node, move caret at first of // the visible character. const WSScanResult scanResultInTextNode =
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
WSRunScanner::Scan::EditableNodes, EditorRawDOMPoint(text, 0),
BlockInlineCheck::UseComputedDisplayStyle); if ((scanResultInTextNode.InVisibleOrCollapsibleCharacters() ||
scanResultInTextNode.ReachedPreformattedLineBreak()) &&
scanResultInTextNode.TextPtr() == text) {
nsresult rv = CollapseSelectionTo(
scanResultInTextNode.PointAtReachedContent<EditorRawDOMPoint>());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed"); return rv;
} // If it's an invisible text node, keep scanning next leaf.
leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
*leafContent,
{LeafNodeType::LeafNodeOrNonEditableNode,
LeafNodeType::LeafNodeOrChildBlock},
BlockInlineCheck::UseComputedDisplayStyle, editingHost); continue;
}
// If there is editable <br> or something void element like <img>, <input>, // <hr> etc, move caret before it. if (!HTMLEditUtils::CanNodeContain(*leafContent, *nsGkAtoms::textTagName) ||
HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafContent)) {
MOZ_ASSERT(leafContent->GetParent()); if (EditorUtils::IsEditableContent(*leafContent, EditorType::HTML)) {
nsresult rv = CollapseSelectionTo(EditorDOMPoint(leafContent));
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed"); return rv;
}
MOZ_ASSERT_UNREACHABLE( "How do we reach editable leaf in non-editable element?"); // But if it's not editable, let's put caret at start of editing host // for now.
nsresult rv = CollapseSelectionTo(EditorDOMPoint(editingHost, 0));
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed"); return rv;
}
// If we meet non-empty block element, we need to scan its child too. if (HTMLEditUtils::IsBlockElement(
*leafContent, BlockInlineCheck::UseComputedDisplayStyle) &&
!HTMLEditUtils::IsEmptyNode(
*leafContent,
{EmptyCheckOption::TreatSingleBRElementAsVisible,
EmptyCheckOption::TreatNonEditableContentAsInvisible}) &&
!HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafContent)) {
leafContent = HTMLEditUtils::GetFirstLeafContent(
*leafContent,
{LeafNodeType::LeafNodeOrNonEditableNode,
LeafNodeType::LeafNodeOrChildBlock},
BlockInlineCheck::UseComputedDisplayStyle, editingHost); continue;
}
// Otherwise, we must meet an empty block element or a data node like // comment node. Let's ignore it.
leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
*leafContent,
{LeafNodeType::LeafNodeOrNonEditableNode,
LeafNodeType::LeafNodeOrChildBlock},
BlockInlineCheck::UseComputedDisplayStyle, editingHost);
}
// If there is no visible/editable node except another block element in // current editing host, we should move caret to very first of the editing // host. // XXX This may not make sense, but Chromium behaves so. Therefore, the // reason why we do this is just compatibility with Chromium.
nsresult rv = CollapseSelectionTo(EditorDOMPoint(editingHost, 0));
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed"); return rv;
}
void HTMLEditor::PreHandleMouseDown(const MouseEvent& aMouseDownEvent) { if (mPendingStylesToApplyToNewContent) { // mPendingStylesToApplyToNewContent will be notified of selection change // even if aMouseDownEvent is not an acceptable event for this editor. // Therefore, we need to notify it of this event too.
mPendingStylesToApplyToNewContent->PreHandleMouseEvent(aMouseDownEvent);
}
}
void HTMLEditor::PreHandleMouseUp(const MouseEvent& aMouseUpEvent) { if (mPendingStylesToApplyToNewContent) { // mPendingStylesToApplyToNewContent will be notified of selection change // even if aMouseUpEvent is not an acceptable event for this editor. // Therefore, we need to notify it of this event too.
mPendingStylesToApplyToNewContent->PreHandleMouseEvent(aMouseUpEvent);
}
}
nsresult HTMLEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) { // NOTE: When you change this method, you should also change: // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html if (NS_WARN_IF(!aKeyboardEvent)) { return NS_ERROR_UNEXPECTED;
}
if (IsReadonly()) {
HandleKeyPressEventInReadOnlyMode(*aKeyboardEvent); return NS_OK;
}
switch (aKeyboardEvent->mKeyCode) { case NS_VK_META: case NS_VK_WIN: case NS_VK_SHIFT: case NS_VK_CONTROL: case NS_VK_ALT: // FYI: This shouldn't occur since modifier key shouldn't cause eKeyPress // event.
aKeyboardEvent->PreventDefault(); return NS_OK;
case NS_VK_BACK: case NS_VK_DELETE: {
nsresult rv = EditorBase::HandleKeyPressEvent(aKeyboardEvent);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::HandleKeyPressEvent() failed"); return rv;
} case NS_VK_TAB: { // Basically, "Tab" key be used only for focus navigation. // FYI: In web apps, this is always true. if (IsTabbable()) { return NS_OK;
}
// If we're in the plaintext mode, and not tabbable editor, let's // insert a horizontal tabulation. if (IsPlaintextMailComposer()) { if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() ||
aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta()) { return NS_OK;
}
// else we insert the tab straight through
aKeyboardEvent->PreventDefault();
nsresult rv = OnInputText(u"\t"_ns);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::OnInputText(\\t) failed"); return rv;
}
// Otherwise, e.g., we're an embedding editor in chrome, we can handle // "Tab" key as an input. if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() ||
aKeyboardEvent->IsMeta()) { return NS_OK;
}
// If selection is in a table element, we need special handling. if (HTMLEditUtils::IsAnyTableElement(editableBlockElement)) {
Result<EditActionResult, nsresult> result =
HandleTabKeyPressInTable(aKeyboardEvent); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("HTMLEditor::HandleTabKeyPressInTable() failed"); return EditorBase::ToGenericNSResult(result.unwrapErr());
} if (!result.inspect().Handled()) { return NS_OK;
}
nsresult rv = ScrollSelectionFocusIntoView();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "EditorBase::ScrollSelectionFocusIntoView() failed"); return EditorBase::ToGenericNSResult(rv);
}
// If selection is in an list item element, treat it as indent or outdent. if (HTMLEditUtils::IsListItem(editableBlockElement)) {
aKeyboardEvent->PreventDefault(); if (!aKeyboardEvent->IsShift()) {
nsresult rv = IndentAsAction();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::IndentAsAction() failed"); return EditorBase::ToGenericNSResult(rv);
}
nsresult rv = OutdentAsAction();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OutdentAsAction() failed"); return EditorBase::ToGenericNSResult(rv);
}
// If only "Tab" key is pressed in normal context, just treat it as // horizontal tab character input. if (aKeyboardEvent->IsShift()) { return NS_OK;
}
aKeyboardEvent->PreventDefault();
nsresult rv = OnInputText(u"\t"_ns);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::OnInputText(\\t) failed"); return EditorBase::ToGenericNSResult(rv);
} case NS_VK_RETURN: { if (!aKeyboardEvent->IsInputtingLineBreak()) { return NS_OK;
} // Anyway consume the event even if we cannot handle it actually because // we've already checked whether the an editing host has focus.
aKeyboardEvent->PreventDefault(); const RefPtr<Element> editingHost =
ComputeEditingHost(LimitInBodyElement::No); if (NS_WARN_IF(!editingHost)) { return NS_ERROR_UNEXPECTED;
} // Shift + Enter should insert a <br> or a LF instead of splitting current // paragraph. Additionally, if we're in plaintext-only mode, we should // do so because Chrome does so, but execCommand("insertParagraph") keeps // working as contenteditable=true. So, we cannot redirect in // InsertParagraphSeparatorAsAction(). if (aKeyboardEvent->IsShift() ||
editingHost->IsContentEditablePlainTextOnly()) { // Only inserts a <br> element.
nsresult rv = InsertLineBreakAsAction();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::InsertLineBreakAsAction() failed"); return EditorBase::ToGenericNSResult(rv);
} // uses rules to figure out what to insert
nsresult rv = InsertParagraphSeparatorAsAction();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "HTMLEditor::InsertParagraphSeparatorAsAction() failed"); return EditorBase::ToGenericNSResult(rv);
}
}
if (!aKeyboardEvent->IsInputtingText()) { // we don't PreventDefault() here or keybindings like control-x won't work return NS_OK;
}
aKeyboardEvent->PreventDefault(); // If we dispatch 2 keypress events for a surrogate pair and we set only // first `.key` value to the surrogate pair, the preceding one has it and the // other has empty string. In this case, we should handle only the first one // with the key value. if (!StaticPrefs::dom_event_keypress_dispatch_once_per_surrogate_pair() &&
!StaticPrefs::dom_event_keypress_key_allow_lone_surrogate() &&
aKeyboardEvent->mKeyValue.IsEmpty() &&
IS_SURROGATE(aKeyboardEvent->mCharCode)) { return NS_OK;
}
nsAutoString str(aKeyboardEvent->mKeyValue); if (str.IsEmpty()) {
str.Assign(static_cast<char16_t>(aKeyboardEvent->mCharCode));
} // FYI: DIfferent from TextEditor, we can treat \r (CR) as-is in HTMLEditor.
nsresult rv = OnInputText(str);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::OnInputText() failed"); return rv;
}
NS_IMETHODIMP HTMLEditor::NodeIsBlock(nsINode* aNode, bool* aIsBlock) { if (NS_WARN_IF(!aNode)) { return NS_ERROR_INVALID_ARG;
} if (MOZ_UNLIKELY(!aNode->IsElement())) {
*aIsBlock = false; return NS_OK;
} // If the node is in composed doc, we'll refer its style. If we don't flush // pending style here, another API call may change the style. Therefore, // let's flush the pending style changes right now. if (aNode->IsInComposedDoc()) { if (RefPtr<PresShell> presShell = GetPresShell()) {
presShell->FlushPendingNotifications(FlushType::Style);
}
}
*aIsBlock = HTMLEditUtils::IsBlockElement(
*aNode->AsElement(), BlockInlineCheck::UseComputedDisplayOutsideStyle); return NS_OK;
}
// Look for an HTML <base> tag
RefPtr<nsContentList> baseElementList =
document->GetElementsByTagName(u"base"_ns);
// If no base tag, then set baseURL to the document's URL. This is very // important, else relative URLs for links and images are wrong if (!baseElementList || !baseElementList->Item(0)) {
document->SetBaseURI(document->GetDocumentURI());
} return NS_OK;
}
AutoEditActionDataSetter dummyEditActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!dummyEditActionData.CanHandle())) { // Do nothing if we didn't find a table cell. return EditActionResult::IgnoredResult();
}
// Find enclosing table cell from selection (cell may be selected element) const RefPtr<Element> cellElement =
GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td); if (!cellElement) {
NS_WARNING( "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td) " "returned nullptr"); // Do nothing if we didn't find a table cell. return EditActionResult::IgnoredResult();
}
// advance to next cell // first create an iterator over the table
PostContentIterator postOrderIter;
nsresult rv = postOrderIter.Init(table); if (NS_FAILED(rv)) {
NS_WARNING("PostContentIterator::Init() failed"); return Err(rv);
} // position postOrderIter at block
rv = postOrderIter.PositionAt(cellElement); if (NS_FAILED(rv)) {
NS_WARNING("PostContentIterator::PositionAt() failed"); return Err(rv);
}
do { if (aKeyboardEvent->IsShift()) {
postOrderIter.Prev();
} else {
postOrderIter.Next();
}
nsCOMPtr<nsINode> node = postOrderIter.GetCurrentNode(); if (node && HTMLEditUtils::IsTableCell(node) &&
HTMLEditUtils::GetClosestAncestorTableElement(*node->AsElement()) ==
table) {
aKeyboardEvent->PreventDefault();
CollapseSelectionToDeepestNonTableFirstChild(node); if (NS_WARN_IF(Destroyed())) { return Err(NS_ERROR_EDITOR_DESTROYED);
} return EditActionResult::HandledResult();
}
} while (!postOrderIter.IsDone());
if (aKeyboardEvent->IsShift()) { return EditActionResult::IgnoredResult();
}
// If we haven't handled it yet, then we must have run off the end of the // table. Insert a new row. // XXX We should investigate whether this behavior is supported by other // browsers later.
AutoEditActionDataSetter editActionData(*this,
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.45 Sekunden
(vorverarbeitet)
¤
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.