tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit d37ff0b95c6020b9c1e506ca21d6458c3c6bc3cd
parent d5e7c19b494ed3306fb4e745597472f05165a663
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date:   Fri, 19 Dec 2025 23:13:06 +0000

Bug 2006216 - Make `HTMLEditor::SelectAllInternal` assume there can be no element in the document r=m_kato

Technically, the document can have no element, e.g.,
```js
document.documentElement.remove();
```
And it should work even if it's destroyed because "Select All" should be
able to work even without `HTMLEditor`.  Therefore, this makes the
method stop using `GetRoot()` which requires `mRootElement`.

Finally, I checked the behavior when there is no element on Chrome.
Then, `document.execCommand("selectAll")` succeeded and returned `true`.
However, they don't touch `Selection` actually.  It's simple since
there is nothing to do.  Let's follow their behavior.

Differential Revision: https://phabricator.services.mozilla.com/D276800

Diffstat:
Meditor/libeditor/HTMLEditor.cpp | 37+++++++++++++++++++++++++------------
Atesting/web-platform/tests/editing/crashtests/selectall-during-setting-innerHTML-of-documentElement.html | 21+++++++++++++++++++++
2 files changed, 46 insertions(+), 12 deletions(-)

diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp @@ -4718,8 +4718,11 @@ nsresult HTMLEditor::SelectAllInternal() { MOZ_ASSERT(IsEditActionDataAvailable()); CommitComposition(); - if (NS_WARN_IF(Destroyed())) { - return NS_ERROR_EDITOR_DESTROYED; + const RefPtr<Document> doc = GetDocument(); + if (NS_WARN_IF(!doc)) { + // The root caller should not use HTMLEditor in this case (mDocument won't + // be cleared except by the cycle collector). + return NS_ERROR_NOT_AVAILABLE; } auto GetBodyElementIfElementIsParentOfHTMLBody = @@ -4734,9 +4737,9 @@ nsresult HTMLEditor::SelectAllInternal() { : const_cast<Element*>(&aElement); }; - nsCOMPtr<nsIContent> selectionRootContent = + const nsCOMPtr<nsIContent> selectionRootContent = [&]() MOZ_CAN_RUN_SCRIPT -> nsIContent* { - RefPtr<Element> elementToBeSelected = [&]() -> Element* { + const RefPtr<Element> elementForComputingSelectionRoot = [&]() -> Element* { // If there is at least one selection range, we should compute the // selection root from the anchor node. if (SelectionRef().RangeCount()) { @@ -4756,18 +4759,22 @@ nsresult HTMLEditor::SelectAllInternal() { 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; + // Okay, there is no selection range and no focused element, let's select + // all of the body or the document element. + if (Element* const bodyElement = GetBodyElement()) { + return bodyElement; + } + return doc->GetDocumentElement(); }(); + if (MOZ_UNLIKELY(!elementForComputingSelectionRoot)) { + return nullptr; + } // Then, compute the selection root content to select all including // elementToBeSelected. RefPtr<PresShell> presShell = GetPresShell(); nsIContent* computedSelectionRootContent = - elementToBeSelected->GetSelectionRootContent( + elementForComputingSelectionRoot->GetSelectionRootContent( presShell, nsINode::IgnoreOwnIndependentSelection::Yes, nsINode::AllowCrossShadowBoundary::No); if (NS_WARN_IF(!computedSelectionRootContent)) { @@ -4779,8 +4786,14 @@ nsresult HTMLEditor::SelectAllInternal() { return GetBodyElementIfElementIsParentOfHTMLBody( *computedSelectionRootContent->AsElement()); }(); - if (NS_WARN_IF(!selectionRootContent)) { - return NS_ERROR_FAILURE; + if (MOZ_UNLIKELY(!selectionRootContent)) { + // If there is no element in the document, the document node can have + // selection range whose container is the document node. However, Chrome + // does not handle "Select All" command in this case (if there is no + // selection range, no new range is created and if there is a collapsed + // range, the range won't be extended to select all children of the + // Document). Let's follow it. + return NS_OK; } Maybe<Selection::AutoUserInitiated> userSelection; diff --git a/testing/web-platform/tests/editing/crashtests/selectall-during-setting-innerHTML-of-documentElement.html b/testing/web-platform/tests/editing/crashtests/selectall-during-setting-innerHTML-of-documentElement.html @@ -0,0 +1,21 @@ +<!doctype html> +<html> +<head> +<meta charset="utf-8"> +<script> +"use strict"; + +addEventListener("DOMContentLoaded", () => { + const script = document.querySelector("script"); + script.appendChild( + document.createElement("iframe") + ); + document.documentElement.innerHTML = + `<body contenteditable><iframe onload="document.execCommand('selectAll')"></iframe>`; +}, {once: true}); +</script> +</head> +<body> + <div contenteditable></div> +</body> +</html>