From fc89971ea6dd75305ae7752c3c20b220a8109c74 Mon Sep 17 00:00:00 2001 From: Masayuki Nakano Date: Thu, 22 Sep 2022 06:27:38 +0000 Subject: [PATCH] Bug 1789967 - part 4: Make `HTMLEditor::SelectAllInternal` work without selection range r=m_kato It may be called even when there is no selection range and focused element. However, it assumes that there is a selection range, and an editable element has focus. Therefore, now, if there is an editing host and user tries to do "Select All" without clicking somewhere before doing it, "Select All" does nothing. Differential Revision: https://phabricator.services.mozilla.com/D157409 --- editor/libeditor/HTMLEditor.cpp | 102 +++++++++----- layout/generic/crashtests/1644819.html | 4 - .../other/selectall-without-focus.html.ini | 3 - .../other/selectall-in-editinghost.html | 125 ++++++++++++++++++ .../other/selectall-without-focus.html | 40 ++++++ 5 files changed, 232 insertions(+), 42 deletions(-) delete mode 100644 testing/web-platform/meta/editing/other/selectall-without-focus.html.ini create mode 100644 testing/web-platform/tests/editing/other/selectall-in-editinghost.html diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp index c82ed0ac7872..f266d61bc0af 100644 --- a/editor/libeditor/HTMLEditor.cpp +++ b/editor/libeditor/HTMLEditor.cpp @@ -4338,52 +4338,84 @@ nsresult HTMLEditor::SelectAllInternal() { return NS_ERROR_EDITOR_DESTROYED; } - // XXX Perhaps, we should check whether we still have focus since composition - // event listener may have already moved focus to different editing - // host or other element. So, perhaps, we need to retrieve anchor node - // before committing composition and check if selection is still in - // same editing host. - - nsINode* anchorNode = SelectionRef().GetAnchorNode(); - if (NS_WARN_IF(!anchorNode) || NS_WARN_IF(!anchorNode->IsContent())) { - return NS_ERROR_FAILURE; - } - - nsCOMPtr anchorContent = anchorNode->AsContent(); - nsCOMPtr rootContent; - if (anchorContent->HasIndependentSelection()) { - SelectionRef().SetAncestorLimiter(nullptr); - rootContent = mRootElement; - if (NS_WARN_IF(!rootContent)) { - return NS_ERROR_UNEXPECTED; + auto GetBodyElementIfElementIsParentOfHTMLBody = + [](const Element& aElement) -> Element* { + if (!aElement.OwnerDoc()->IsHTMLDocument()) { + return const_cast(&aElement); } - } else { - RefPtr presShell = GetPresShell(); - rootContent = anchorContent->GetSelectionRootContent(presShell); - if (NS_WARN_IF(!rootContent)) { - return NS_ERROR_UNEXPECTED; - } - // If the document is HTML document (not XHTML document), we should - // select all children of the `` element instead of `` - // element. - if (Document* document = GetDocument()) { - if (document->IsHTMLDocument()) { - if (HTMLBodyElement* bodyElement = document->GetBodyElement()) { - if (nsContentUtils::ContentIsFlattenedTreeDescendantOf(bodyElement, - rootContent)) { - rootContent = bodyElement; + HTMLBodyElement* bodyElement = aElement.OwnerDoc()->GetBodyElement(); + return bodyElement && nsContentUtils::ContentIsFlattenedTreeDescendantOf( + bodyElement, &aElement) + ? bodyElement + : const_cast(&aElement); + }; + + nsCOMPtr selectionRootContent = + [&]() MOZ_CAN_RUN_SCRIPT -> nsIContent* { + RefPtr elementToBeSelected = [&]() -> Element* { + // If there is at least one selection range, we should compute the + // selection root from the anchor node. + if (SelectionRef().RangeCount()) { + if (nsIContent* content = + nsIContent::FromNodeOrNull(SelectionRef().GetAnchorNode())) { + if (content->IsElement()) { + return content->AsElement(); + } + if (Element* parentElement = + content->GetParentElementCrossingShadowRoot()) { + return parentElement; } } } + // If no element contains a selection range, we should select all children + // of the focused element at least. + if (Element* focusedElement = GetFocusedElement()) { + return focusedElement; + } + // of the body or document element. + Element* bodyOrDocumentElement = GetRoot(); + NS_WARNING_ASSERTION(bodyOrDocumentElement, + "There was no element in the document"); + return bodyOrDocumentElement; + }(); + + // If the element to be selected is or following editable text"; + getSelection().collapse(editingHost.querySelector("textarea"), 0); + document.execCommand("selectAll"); + assert_false( + getSelection().isCollapsed, + 'Selection should not be collapsed after calling document.execCommand("selectAll")' + ); + const rangeText = getSelection().toString(); + assert_false( + rangeText.includes("preceding text"), + "Selection should not contain the preceding text of the editing host" + ); + assert_true( + rangeText.includes("preceding editable text"), + "Selection should contain the preceding editable text of "; + getSelection().collapse(document.querySelector("textarea"), 0); + document.execCommand("selectAll"); + assert_false( + getSelection().isCollapsed, + 'Selection should not be collapsed after calling document.execCommand("selectAll")' + ); + const rangeText = getSelection().toString(); + assert_true( + rangeText.includes("preceding text"), + "Selection should contain the preceding text of the editing host" + ); + assert_true( + rangeText.includes("editable text"), + "Selection should contain the editable text in the editing host" + ); + getSelection().removeAllRanges(); + }, "execCommand('selectAll') should select all content in the document when selection is in