/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 sw=2 et tw=78: */ /* 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/. */
if (NS_WARN_IF(!mInitSucceeded)) { return NS_ERROR_NOT_INITIALIZED;
}
// force IME commit; set up rules sniffing and batching
DebugOnly<nsresult> rvIgnored = CommitComposition();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "EditorBase::CommitComposition() failed, but ignored"); if (NS_WARN_IF(Destroyed())) { return NS_ERROR_EDITOR_DESTROYED;
}
// Delete Selection, but only if it isn't collapsed, see bug #106269 if (!SelectionRef().IsCollapsed()) {
nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip); if (NS_FAILED(rv)) {
NS_WARNING( "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed"); return rv;
}
}
// Get the first range in the selection, for context:
RefPtr<const nsRange> range = SelectionRef().GetRangeAt(0); if (NS_WARN_IF(!range)) { return NS_ERROR_FAILURE;
}
// Create fragment for pasted HTML.
ErrorResult error;
RefPtr<DocumentFragment> documentFragment =
range->CreateContextualFragment(aInputString, error); if (error.Failed()) {
NS_WARNING("nsRange::CreateContextualFragment() failed"); return error.StealNSResult();
}
// Put the fragment into the document at start of selection.
EditorDOMPoint pointToInsert(range->StartRef()); // XXX We need to make pointToInsert store offset for keeping traditional // behavior since using only child node to pointing insertion point // changes the behavior when inserted child is moved by mutation // observer. We need to investigate what we should do here.
Unused << pointToInsert.Offset();
EditorDOMPoint pointToPutCaret; for (nsCOMPtr<nsIContent> contentToInsert = documentFragment->GetFirstChild();
contentToInsert; contentToInsert = documentFragment->GetFirstChild()) {
Result<CreateContentResult, nsresult> insertChildContentNodeResult =
InsertNodeWithTransaction(*contentToInsert, pointToInsert); if (MOZ_UNLIKELY(insertChildContentNodeResult.isErr())) {
NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); return insertChildContentNodeResult.unwrapErr();
}
CreateContentResult unwrappedInsertChildContentNodeResult =
insertChildContentNodeResult.unwrap();
unwrappedInsertChildContentNodeResult.MoveCaretPointTo(
pointToPutCaret, *this,
{SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); // XXX If the inserted node has been moved by mutation observer, // incrementing offset will cause odd result. Next new node // will be inserted after existing node and the offset will be // overflown from the container node.
pointToInsert.Set(pointToInsert.GetContainer(), pointToInsert.Offset() + 1); if (NS_WARN_IF(!pointToInsert.Offset())) { // Append the remaining children to the container if offset is // overflown.
pointToInsert.SetToEndOf(pointToInsert.GetContainer());
}
}
if (pointToPutCaret.IsSet()) {
nsresult rv = CollapseSelectionTo(pointToPutCaret); if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored"); return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed, but ignored");
}
nsresult HTMLEditor::InsertHTMLAsAction(const nsAString& aInString,
nsIPrincipal* aPrincipal) { // FIXME: This should keep handling inserting HTML if the caller is // nsIHTMLEditor::InsertHTML. if (IsReadonly()) { return NS_OK;
}
private: class FragmentFromPasteCreator; class FragmentParser; /** * CollectTopMostChildContentsCompletelyInRange() collects topmost child * contents which are completely in the given range. * For example, if the range points a node with its container node, the * result is only the node (meaning does not include its descendants). * If the range starts start of a node and ends end of it, and if the node * does not have children, returns no nodes, otherwise, if the node has * some children, the result includes its all children (not including their * descendants). * * @param aStartPoint Start point of the range. * @param aEndPoint End point of the range. * @param aOutArrayOfContents [Out] Topmost children which are completely in * the range.
*/ staticvoid CollectTopMostChildContentsCompletelyInRange( const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint,
nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents);
/** * @return nullptr, if there's no invisible `<br>`.
*/
HTMLBRElement* GetInvisibleBRElementAtPoint( const EditorDOMPoint& aPointToInsert) const;
/** * @return error result or the last inserted point. The latter is only set, if * content was inserted.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
InsertContents( const EditorDOMPoint& aPointToInsert,
nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents, const nsINode* aFragmentAsNode);
// MOZ_KNOWN_LIVE because this is set only by the constructor which is // marked as MOZ_CAN_RUN_SCRIPT and this is allocated only in the stack.
MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor;
MOZ_KNOWN_LIVE const Element& mEditingHost;
};
/** * This is designed for a helper class to remove disturbing nodes at inserting * the HTML fragment into the DOM tree. This walks the children and if some * elements do not have enough children, e.g., list elements not having * another visible list elements nor list item elements, * will be removed. * * @param aNode Should not be a node whose mutation may be observed by * JS.
*/ staticvoid RemoveIncompleteDescendantsFromInsertingFragment(nsINode& aNode);
enumclass NodesToRemove {
eAll,
eOnlyListItems /*!< List items are always block-level elements, hence such
whitespace-only nodes are always invisible. */
}; static nsresult
RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
nsIContent& aNode, NodesToRemove aNodesToRemove);
};
// but don't cross tables
nsIContent* containerContent = nullptr; if (!HTMLEditUtils::IsTable(aLastInsertedPoint.GetChild())) {
containerContent = HTMLEditUtils::GetLastLeafContent(
*aLastInsertedPoint.GetChild(), {LeafNodeType::OnlyEditableLeafNode},
BlockInlineCheck::Unused,
aLastInsertedPoint.GetChild()->GetAsElementOrParentElement()); if (containerContent) {
Element* mostDistantInclusiveAncestorTableElement = nullptr; for (Element* maybeTableElement =
containerContent->GetAsElementOrParentElement();
maybeTableElement &&
maybeTableElement != aLastInsertedPoint.GetChild();
maybeTableElement = maybeTableElement->GetParentElement()) { if (HTMLEditUtils::IsTable(maybeTableElement)) {
mostDistantInclusiveAncestorTableElement = maybeTableElement;
}
} // If we're in table elements, we should put caret into the most ancestor // table element. if (mostDistantInclusiveAncestorTableElement) {
containerContent = mostDistantInclusiveAncestorTableElement;
}
}
} // If we are not in table elements, we should put caret in the last inserted // node. if (!containerContent) {
containerContent = aLastInsertedPoint.GetChild();
}
// If the container is a text node or a container element except `<table>` // element, put caret a end of it. if (containerContent->IsText() ||
(HTMLEditUtils::IsContainerNode(*containerContent) &&
!HTMLEditUtils::IsTable(containerContent))) {
pointToPutCaret.SetToEndOf(containerContent);
} // Otherwise, i.e., it's an atomic element, `<table>` element or data node, // put caret after it. else {
pointToPutCaret.Set(containerContent);
DebugOnly<bool> advanced = pointToPutCaret.AdvanceOffset();
NS_WARNING_ASSERTION(advanced, "Failed to advance offset from found node");
}
// Make sure we don't end up with selection collapsed after an invisible // `<br>` element. const WSRunScanner wsRunScannerAtCaret(
WSRunScanner::Scan::EditableNodes, pointToPutCaret,
BlockInlineCheck::UseComputedDisplayStyle); if (wsRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToPutCaret)
.ReachedInvisibleBRElement()) { const WSRunScanner wsRunScannerAtStartReason(
WSRunScanner::Scan::EditableNodes,
EditorDOMPoint(wsRunScannerAtCaret.GetStartReasonContent()),
BlockInlineCheck::UseComputedDisplayStyle); const WSScanResult backwardScanFromPointToCaretResult =
wsRunScannerAtStartReason.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
pointToPutCaret); if (backwardScanFromPointToCaretResult.InVisibleOrCollapsibleCharacters()) {
pointToPutCaret = backwardScanFromPointToCaretResult
.PointAfterReachedContent<EditorDOMPoint>();
} elseif (backwardScanFromPointToCaretResult.ReachedSpecialContent()) { // XXX In my understanding, this is odd. The end reason may not be // same as the reached special content because the equality is // guaranteed only when ReachedCurrentBlockBoundary() returns true. // However, looks like that this code assumes that // GetStartReasonContent() returns the content.
NS_ASSERTION(wsRunScannerAtStartReason.GetStartReasonContent() ==
backwardScanFromPointToCaretResult.GetContent(), "Start reason is not the reached special content");
pointToPutCaret.SetAfter(
wsRunScannerAtStartReason.GetStartReasonContent());
}
}
{
Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); return result.unwrapErr();
} if (result.inspect().Canceled()) { return NS_OK;
}
}
// If we have a destination / target node, we want to insert there rather than // in place of the selection. Ignore aDeleteSelectedContent here if // aPointToInsert is not set since deletion will also occur later in // HTMLWithContextInserter and will be collapsed around there; this block // is intended to cover the various scenarios where we are dropping in an // editor (and may want to delete the selection before collapsing the // selection in the new destination) if (aPointToInsert.IsSet()) {
nsresult rv =
PrepareToInsertContent(aPointToInsert, aDeleteSelectedContent); if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::PrepareToInsertContent() failed"); return rv;
}
aDeleteSelectedContent = DeleteSelectedContent::No;
}
Result<EditActionResult, nsresult> result = htmlWithContextInserter.Run(
aInputString, aContextStr, aInfoStr, aSafeToInsertData,
aInlineStylesAtInsertionPoint); if (MOZ_UNLIKELY(result.isErr())) { return result.unwrapErr();
}
// If nothing is inserted and delete selection is required, we need to // delete selection right now. if (result.inspect().Ignored() &&
aDeleteSelectedContent == DeleteSelectedContent::Yes) {
nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip); if (NS_FAILED(rv)) {
NS_WARNING( "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed"); return rv;
}
} return NS_OK;
}
// create a dom document fragment that represents the structure to paste
nsCOMPtr<nsINode> fragmentAsNode, streamStartParent, streamEndParent;
uint32_t streamStartOffset = 0, streamEndOffset = 0;
// we need to recalculate various things based on potentially new offsets // this is work to be completed at a later date (probably by jfrancis)
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfTopMostChildContents; // If we have stream start point information, lets use it and end point. // Otherwise, we should make a range all over the document fragment.
EditorRawDOMPoint streamStartPoint =
streamStartParent
? EditorRawDOMPoint(streamStartParent,
AssertedCast<uint32_t>(streamStartOffset))
: EditorRawDOMPoint(fragmentAsNode, 0);
EditorRawDOMPoint streamEndPoint =
streamStartParent ? EditorRawDOMPoint(streamEndParent, streamEndOffset)
: EditorRawDOMPoint::AtEndOf(fragmentAsNode);
if (arrayOfTopMostChildContents.IsEmpty()) { return EditActionResult::IgnoredResult(); // Nothing to insert.
}
// Are there any table elements in the list? // check for table cell selection mode bool cellSelectionMode =
HTMLEditUtils::IsInTableCellSelectionMode(mHTMLEditor.SelectionRef());
if (cellSelectionMode) { // do we have table content to paste? If so, we want to delete // the selected table cells and replace with new table elements; // but if not we want to delete _contents_ of cells and replace // with non-table elements. Use cellSelectionMode bool to // indicate results. if (!HTMLEditUtils::IsAnyTableElement(arrayOfTopMostChildContents[0])) {
cellSelectionMode = false;
}
}
if (!cellSelectionMode) {
rv = mHTMLEditor.DeleteSelectionAndPrepareToCreateNode(); if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed"); return Err(rv);
}
if (aInlineStylesAtInsertionPoint == InlineStylesAtInsertionPoint::Clear) { // pasting does not inherit local inline styles
Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
mHTMLEditor.ClearStyleAt(
EditorDOMPoint(mHTMLEditor.SelectionRef().AnchorRef()),
EditorInlineStyle::RemoveAllStyles(), SpecifiedStyle::Preserve,
mEditingHost); if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
NS_WARNING("HTMLEditor::ClearStyleAt() failed"); return pointToPutCaretOrError.propagateErr();
} if (pointToPutCaretOrError.inspect().IsSetAndValid()) {
nsresult rv =
mHTMLEditor.CollapseSelectionTo(pointToPutCaretOrError.unwrap()); if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::CollapseSelectionTo() failed"); return Err(rv);
}
}
}
} else { // Delete whole cells: we will replace with new table content.
// Braces for artificial block to scope AutoSelectionRestorer. // Save current selection since DeleteTableCellWithTransaction() perturbs // it.
{
AutoSelectionRestorer restoreSelectionLater(&mHTMLEditor);
rv = mHTMLEditor.DeleteTableCellWithTransaction(1); if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::DeleteTableCellWithTransaction(1) failed"); return Err(rv);
}
} // collapse selection to beginning of deleted table content
IgnoredErrorResult ignoredError;
mHTMLEditor.SelectionRef().CollapseToStart(ignoredError);
NS_WARNING_ASSERTION(!ignoredError.Failed(), "Selection::Collapse() failed, but ignored");
}
{
Result<EditActionResult, nsresult> result =
mHTMLEditor.CanHandleHTMLEditSubAction(); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); return result;
} if (result.inspect().Canceled()) { return result;
}
}
mHTMLEditor.UndefineCaretBidiLevel();
rv = mHTMLEditor.EnsureNoPaddingBRElementForEmptyEditor(); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " "failed, but ignored");
if (NS_SUCCEEDED(rv) && mHTMLEditor.SelectionRef().IsCollapsed()) {
nsresult rv =
mHTMLEditor.EnsureCaretNotAfterInvisibleBRElement(mEditingHost); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " "failed, but ignored"); if (NS_SUCCEEDED(rv)) {
nsresult rv = mHTMLEditor.PrepareInlineStylesForCaret(); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
}
}
// Adjust position based on the first node we are going to insert. constauto candidatePointToInsert =
mHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>(); if (NS_WARN_IF(!candidatePointToInsert.IsSet()) ||
NS_WARN_IF(
!candidatePointToInsert.GetContainer()->IsInclusiveDescendantOf(
&mEditingHost))) { return Err(NS_ERROR_FAILURE);
}
EditorDOMPoint pointToInsert =
HTMLEditUtils::GetBetterInsertionPointFor<EditorDOMPoint>(
arrayOfTopMostChildContents[0],
mHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>()); if (!pointToInsert.IsSet()) {
NS_WARNING("HTMLEditor::GetBetterInsertionPointFor() failed"); return Err(NS_ERROR_FAILURE);
}
{ // Block only for AutoHTMLFragmentBoundariesFixer to hide it from the // following code. Note that it may modify arrayOfTopMostChildContents.
AutoHTMLFragmentBoundariesFixer fixPiecesOfTablesAndLists(
arrayOfTopMostChildContents);
}
if (MOZ_UNLIKELY(!lastInsertedPoint.inspect().IsInComposedDoc())) { return EditActionResult::HandledResult();
}
if (MOZ_LIKELY(lastInsertedPoint.inspect().IsInContentNode())) { constauto afterLastInsertedContent =
lastInsertedPoint.inspect().NextPointOrAfterContainer(); if (MOZ_LIKELY(afterLastInsertedContent.IsInContentNode())) {
nsresult rv = mHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak(
afterLastInsertedContent); if (NS_FAILED(rv)) {
NS_WARNING( "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); return Err(rv);
}
}
}
const EditorDOMPoint pointToPutCaret =
GetNewCaretPointAfterInsertingHTML(lastInsertedPoint.inspect()); // Now collapse the selection to the end of what we just inserted.
rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret); if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING( "EditorBase::CollapseSelectionTo() caused destroying the editor"); return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed, but ignored");
// If we didn't start from an `<a href>` element, we should not keep // caret in the link to make users type something outside the link. if (insertionPointWasInLink) { return EditActionResult::HandledResult();
}
RefPtr<Element> linkElement = GetLinkElement(pointToPutCaret.GetContainer());
if (!linkElement) { return EditActionResult::HandledResult();
}
// Loop over the node list and paste the nodes: const RefPtr<const Element> maybeNonEditableBlockElement =
pointToInsert.IsInContentNode()
? HTMLEditUtils::GetInclusiveAncestorElement(
*pointToInsert.ContainerAs<nsIContent>(),
HTMLEditUtils::ClosestBlockElement,
BlockInlineCheck::UseComputedDisplayOutsideStyle)
: nullptr;
EditorDOMPoint lastInsertedPoint;
nsCOMPtr<nsIContent> insertedContextParentContent; for (OwningNonNull<nsIContent>& content : aArrayOfTopMostChildContents) { if (NS_WARN_IF(content == aFragmentAsNode) ||
NS_WARN_IF(content->IsHTMLElement(nsGkAtoms::body))) { return Err(NS_ERROR_FAILURE);
}
if (insertedContextParentContent) { // If we had to insert something higher up in the paste hierarchy, // we want to skip any further paste nodes that descend from that. // Else we will paste twice. // XXX This check may be really expensive. Cannot we check whether // the node's `ownerDocument` is the `aFragmentAsNode` or not? // XXX If content was moved to outside of insertedContextParentContent // by mutation event listeners, we will anyway duplicate it. if (EditorUtils::IsDescendantOf(*content,
*insertedContextParentContent)) { continue;
}
}
// If a `<table>` or `<tr>` element on the clipboard, and pasting it into // a `<table>` or `<tr>` element, insert only the appropriate children // instead. bool inserted = false; if (HTMLEditUtils::IsTableRow(content) &&
HTMLEditUtils::IsTableRow(pointToInsert.GetContainer()) &&
(HTMLEditUtils::IsTable(content) ||
HTMLEditUtils::IsTable(pointToInsert.GetContainer()))) { // Move children of current node to the insertion point.
AutoTArray<OwningNonNull<nsIContent>, 24> children;
HTMLEditUtils::CollectAllChildren(*content, children);
EditorDOMPoint pointToPutCaret; for (const OwningNonNull<nsIContent>& child : children) { // MOZ_KnownLive(child) because of bug 1622253
Result<CreateContentResult, nsresult> moveChildResult =
mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
MOZ_KnownLive(child), pointToInsert,
SplitAtEdges::eDoNotCreateEmptyContainer); if (MOZ_UNLIKELY(moveChildResult.isErr())) { // If moving node is moved to different place, we should ignore // this result and keep trying to insert next content node to same // position. if (moveChildResult.inspectErr() ==
NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
inserted = true; continue; // the inner `for` loop
}
NS_WARNING( "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" "SplitAtEdges::eDoNotCreateEmptyContainer) failed, maybe " "ignored"); break; // from the inner `for` loop
} if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { continue;
}
inserted = true;
lastInsertedPoint.Set(child);
pointToInsert = lastInsertedPoint.NextPoint();
MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
CreateContentResult unwrappedMoveChildResult = moveChildResult.unwrap();
unwrappedMoveChildResult.MoveCaretPointTo(
pointToPutCaret, mHTMLEditor,
{SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
} // end of the inner `for` loop
if (pointToPutCaret.IsSet()) {
nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret); if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING( "EditorBase::CollapseSelectionTo() caused destroying the editor"); return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed, but ignored");
}
} // If a list element on the clipboard, and pasting it into a list or // list item element, insert the appropriate children instead. I.e., // merge the list elements instead of pasting as a sublist. elseif (HTMLEditUtils::IsAnyListElement(content) &&
(HTMLEditUtils::IsAnyListElement(pointToInsert.GetContainer()) ||
HTMLEditUtils::IsListItem(pointToInsert.GetContainer()))) {
AutoTArray<OwningNonNull<nsIContent>, 24> children;
HTMLEditUtils::CollectAllChildren(*content, children);
EditorDOMPoint pointToPutCaret; for (const OwningNonNull<nsIContent>& child : children) { if (HTMLEditUtils::IsListItem(child) ||
HTMLEditUtils::IsAnyListElement(child)) { // If we're pasting into empty list item, we should remove it // and past current node into the parent list directly. // XXX This creates invalid structure if current list item element // is not proper child of the parent element, or current node // is a list element. if (HTMLEditUtils::IsListItem(pointToInsert.GetContainer()) &&
HTMLEditUtils::IsEmptyNode(
*pointToInsert.GetContainer(),
{EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
NS_WARNING_ASSERTION(pointToInsert.GetContainerParent(), "Insertion point is out of the DOM tree"); if (pointToInsert.GetContainerParent()) {
pointToInsert.Set(pointToInsert.GetContainer());
MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
nsresult rv = mHTMLEditor.DeleteNodeWithTransaction(
MOZ_KnownLive(*pointToInsert.GetChild())); if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::DeleteNodeWithTransaction() " "failed, but ignored");
}
} // MOZ_KnownLive(child) because of bug 1622253
Result<CreateContentResult, nsresult> moveChildResult =
mHTMLEditor
.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
MOZ_KnownLive(child), pointToInsert,
SplitAtEdges::eDoNotCreateEmptyContainer); if (MOZ_UNLIKELY(moveChildResult.isErr())) { // If moving node is moved to different place, we should ignore // this result and keep trying to insert next content node to // same position. if (moveChildResult.inspectErr() ==
NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
inserted = true; continue; // the inner `for` loop
} if (NS_WARN_IF(moveChildResult.inspectErr() ==
NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING( "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe " "ignored"); break; // from the inner `for` loop
} if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { continue;
}
inserted = true;
lastInsertedPoint.Set(child);
pointToInsert = lastInsertedPoint.NextPoint();
MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
CreateContentResult unwrappedMoveChildResult =
moveChildResult.unwrap();
unwrappedMoveChildResult.MoveCaretPointTo(
pointToPutCaret, mHTMLEditor,
{SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
} // If the child of current node is not list item nor list element, // we should remove it from the DOM tree. elseif (HTMLEditUtils::IsRemovableNode(child)) {
AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
IgnoredErrorResult ignoredError;
content->RemoveChild(child, ignoredError); if (MOZ_UNLIKELY(mHTMLEditor.Destroyed())) {
NS_WARNING( "nsIContent::RemoveChild() caused destroying the editor"); return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(!ignoredError.Failed(), "nsINode::RemoveChild() failed, but ignored");
} else {
NS_WARNING( "Failed to delete the first child of a list element because the " "list element non-editable"); break; // from the inner `for` loop
}
} // end of the inner `for` loop
if (MOZ_UNLIKELY(mHTMLEditor.Destroyed())) {
NS_WARNING("The editor has been destroyed"); return Err(NS_ERROR_EDITOR_DESTROYED);
} if (pointToPutCaret.IsSet()) {
nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret); if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING( "EditorBase::CollapseSelectionTo() caused destroying the editor"); return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed, but ignored");
}
} // If pasting into a `<pre>` element and current node is a `<pre>` element, // move only its children. elseif (HTMLEditUtils::IsPre(maybeNonEditableBlockElement) &&
HTMLEditUtils::IsPre(content)) { // Check for pre's going into pre's.
AutoTArray<OwningNonNull<nsIContent>, 24> children;
HTMLEditUtils::CollectAllChildren(*content, children);
EditorDOMPoint pointToPutCaret; for (const OwningNonNull<nsIContent>& child : children) { // MOZ_KnownLive(child) because of bug 1622253
Result<CreateContentResult, nsresult> moveChildResult =
mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
MOZ_KnownLive(child), pointToInsert,
SplitAtEdges::eDoNotCreateEmptyContainer); if (MOZ_UNLIKELY(moveChildResult.isErr())) { // If moving node is moved to different place, we should ignore // this result and keep trying to insert next content node there. if (moveChildResult.inspectErr() ==
NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
inserted = true; continue; // the inner `for` loop
} if (NS_WARN_IF(moveChildResult.inspectErr() ==
NS_ERROR_EDITOR_DESTROYED)) { return moveChildResult.propagateErr();
}
NS_WARNING( "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe " "ignored"); break; // from the inner `for` loop
} if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { continue;
}
CreateContentResult unwrappedMoveChildResult = moveChildResult.unwrap();
inserted = true;
lastInsertedPoint.Set(child);
pointToInsert = lastInsertedPoint.NextPoint();
MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
unwrappedMoveChildResult.MoveCaretPointTo(
pointToPutCaret, mHTMLEditor,
{SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
} // end of the inner `for` loop
if (pointToPutCaret.IsSet()) {
nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret); if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING( "EditorBase::CollapseSelectionTo() caused destroying the editor"); return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed, but ignored");
}
}
// TODO: For making the above code clearer, we should move this fallback // path into a lambda and call it in each if/else-if block. // If we haven't inserted current node nor its children, move current node // to the insertion point. if (!inserted) { // MOZ_KnownLive(content) because 'aArrayOfTopMostChildContents' is // guaranteed to keep it alive.
Result<CreateContentResult, nsresult> moveContentResult =
mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
MOZ_KnownLive(content), pointToInsert,
SplitAtEdges::eDoNotCreateEmptyContainer); if (MOZ_LIKELY(moveContentResult.isOk())) { if (MOZ_UNLIKELY(!moveContentResult.inspect().Handled())) { continue;
}
lastInsertedPoint.Set(content);
pointToInsert = lastInsertedPoint;
MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
nsresult rv = moveContentResult.inspect().SuggestCaretPointTo(
mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError}); if (NS_FAILED(rv)) {
NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed"); return Err(rv);
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, "CreateContentResult::SuggestCaretPointTo() failed, but ignored");
} elseif (moveContentResult.inspectErr() ==
NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) { // Moving node is moved to different place, we should keep trying to // insert the next content to same position.
} else {
NS_WARNING( "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but ignored"); // Assume failure means no legal parent in the document hierarchy, // try again with the parent of content in the paste hierarchy. // FYI: We cannot use `InclusiveAncestorOfType` here because of // calling `InsertNodeIntoProperAncestorWithTransaction()`. for (nsCOMPtr<nsIContent> childContent = content; childContent;
childContent = childContent->GetParent()) { if (NS_WARN_IF(!childContent->GetParent()) ||
NS_WARN_IF(
childContent->GetParent()->IsHTMLElement(nsGkAtoms::body))) { break; // for the inner `for` loop
} const OwningNonNull<nsIContent> oldParentContent =
*childContent->GetParent();
Result<CreateContentResult, nsresult> moveParentResult =
mHTMLEditor
.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
oldParentContent, pointToInsert,
SplitAtEdges::eDoNotCreateEmptyContainer); if (MOZ_UNLIKELY(moveParentResult.isErr())) { // Moving node is moved to different place, we should keep trying to // insert the next content to same position. if (moveParentResult.inspectErr() ==
NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) { break; // from the inner `for` loop
} if (NS_WARN_IF(moveParentResult.inspectErr() ==
NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING( "HTMLEditor::InsertNodeInToProperAncestorWithTransaction(" "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but " "ignored"); continue; // the inner `for` loop
} if (MOZ_UNLIKELY(!moveParentResult.inspect().Handled())) { continue;
}
insertedContextParentContent = oldParentContent;
pointToInsert.Set(oldParentContent);
MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
nsresult rv = moveParentResult.inspect().SuggestCaretPointTo(
mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError}); if (NS_FAILED(rv)) {
NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed"); return Err(rv);
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, "CreateContentResult::SuggestCaretPointTo() failed, but ignored"); break; // from the inner `for` loop
} // end of the inner `for` loop
}
} if (lastInsertedPoint.IsSet()) { if (MOZ_UNLIKELY(lastInsertedPoint.GetContainer() !=
lastInsertedPoint.GetChild()->GetParentNode())) {
NS_WARNING( "HTMLEditor::InsertHTMLWithContextAsSubAction() got lost insertion " "point"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
pointToInsert = lastInsertedPoint.NextPoint();
MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
}
} // end of the `for` loop
// The reason why do that instead of just moving caret after it is, the // link might have ended in an invisible `<br>` element. If so, the code // above just placed selection inside that. So we need to split it instead. // XXX Sounds like that it's not really expensive comparing with the reason // to use SplitNodeDeepWithTransaction() here.
Result<SplitNodeResult, nsresult> splitLinkResult =
mHTMLEditor.SplitNodeDeepWithTransaction(
aLinkElement, aPointToPutCaret,
SplitAtEdges::eDoNotCreateEmptyContainer); if (MOZ_UNLIKELY(splitLinkResult.isErr())) { if (splitLinkResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING( "HTMLEditor::SplitNodeDeepWithTransaction() failed, but ignored");
}
if (nsIContent* previousContentOfSplitPoint =
splitLinkResult.inspect().GetPreviousContent()) {
splitLinkResult.inspect().IgnoreCaretPointSuggestion();
nsresult rv = mHTMLEditor.CollapseSelectionTo(
EditorRawDOMPoint::After(*previousContentOfSplitPoint)); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed, but ignored"); return NS_OK;
}
nsresult HTMLEditor::HTMLTransferablePreparer::Run() { // Create generic Transferable for getting the data
nsresult rv;
RefPtr<nsITransferable> transferable =
do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); if (NS_FAILED(rv)) {
NS_WARNING("do_CreateInstance() failed to create nsITransferable instance"); return rv;
}
if (!transferable) {
NS_WARNING("do_CreateInstance() returned nullptr, but ignored"); return NS_OK;
}
// Get the nsITransferable interface for getting the data from the clipboard
RefPtr<Document> destdoc = mHTMLEditor.GetDocument();
nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
DebugOnly<nsresult> rvIgnored = transferable->Init(loadContext);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::Init() failed, but ignored");
// See `HTMLEditor::InsertFromTransferableAtSelection`.
AddDataFlavorsInBestOrder(*transferable);
transferable.forget(mTransferable);
return NS_OK;
}
void HTMLEditor::HTMLTransferablePreparer::AddDataFlavorsInBestOrder(
nsITransferable& aTransferable) const { // Create the desired DataFlavor for the type of data // we want to get out of the transferable // This should only happen in html editors, not plaintext // Note that if you add more flavors here you will need to add them // to DataTransfer::GetExternalClipboardFormats as well. if (!mHTMLEditor.IsPlaintextMailComposer() &&
!(mEditingHost && mEditingHost->IsContentEditablePlainTextOnly())) {
DebugOnly<nsresult> rvIgnored =
aTransferable.AddDataFlavor(kNativeHTMLMime);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kNativeHTMLMime) failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kHTMLMime);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kHTMLMime) failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kFileMime);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kFileMime) failed, but ignored");
switch (Preferences::GetInt("clipboard.paste_image_type", 1)) { case 0: // prefer JPEG over PNG over GIF encoding
rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kJPEGImageMime) " "failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kJPGImageMime) " "failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kPNGImageMime) " "failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kGIFImageMime) " "failed, but ignored"); break; case 1: // prefer PNG over JPEG over GIF encoding (default) default:
rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kPNGImageMime) " "failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kJPEGImageMime) " "failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kJPGImageMime) " "failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kGIFImageMime) " "failed, but ignored"); break; case 2: // prefer GIF over JPEG over PNG encoding
rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kGIFImageMime) " "failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kJPEGImageMime) " "failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kJPGImageMime) " "failed, but ignored");
rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.30 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.