Bug 1623918 - part 1: Make `nsINode::GetTextEditorRootContent()` handle `TextControlElement` after stopping climbing the DOM tree up r=smaug

It was designed for retrieving associated `TextEditor` and its root content
(anonymous `<div>` element) if the node is in native anonymous subtree in a
text editor or if the node itself is a `TextControlElement`.  Additionally,
`TextControlElement` cannot be nested.  Therefore, it can stop climbing up the
DOM tree when it meets a `TextControlElement`.

Then, we can rewrite this without a loop implemented by itself.  Instead,
it can use `GetClosestNativeAnonymousSubtreeRootParent()` when the node is
in native anonymous subtree.  Otherwise, it just needs to check whether it's
a `TextControlElement` or not.  Therefore, we can make it stop using
`InclusiveAncestorsOfType`.

Finally, it calls `TextControlElement::GetTextEditor()` which is marked as
`MOZ_CAN_RUN_SCRIPT`.  And I think that it may cause running selection
listeners (mutation event listeners won't run because changes occur only in
the native anonymous subtree).  Therefore, we should mark all callers of
it with `MOZ_CAN_RUN_SCRIPT` later.

Differential Revision: https://phabricator.services.mozilla.com/D92728
This commit is contained in:
Masayuki Nakano 2020-10-09 02:36:30 +00:00
Родитель fb7e5d1499
Коммит 235177bdeb
4 изменённых файлов: 60 добавлений и 22 удалений

Просмотреть файл

@ -0,0 +1,16 @@
<html>
<head>
<script>
window.addEventListener('load', () => {
const anchor = document.getElementById('id_7')
anchor.contentEditable = 'true'
anchor.spellcheck = false
const input = document.createElementNS('http://www.w3.org/1999/xhtml', 'input')
anchor.appendChild(input)
const selection = self.getSelection()
selection.selectAllChildren(input)
})
</script>
</head>
<a id='id_7'></a>
</html>

Просмотреть файл

@ -256,5 +256,6 @@ load eventSource_invalid_scheme_worker_shutdown.html
load 1291535.html load 1291535.html
skip-if(!isDebugBuild||xulRuntime.OS!="Linux") load 1611853.html skip-if(!isDebugBuild||xulRuntime.OS!="Linux") load 1611853.html
load 1619322.html load 1619322.html
asserts(0-1) load 1623918.html # May hit an assertion if the <input> element's anonymous tree hasn't been flushed when IMEContentObserver handles focus
load 1656925.html load 1656925.html
skip-if(Android) load 1665792.html # Print preview on android doesn't fly skip-if(Android) load 1665792.html # Print preview on android doesn't fly

Просмотреть файл

@ -26,6 +26,7 @@
#include "mozilla/PresShell.h" #include "mozilla/PresShell.h"
#include "mozilla/ServoBindings.h" #include "mozilla/ServoBindings.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
#include "mozilla/TextControlElement.h"
#include "mozilla/TextEditor.h" #include "mozilla/TextEditor.h"
#include "mozilla/TimeStamp.h" #include "mozilla/TimeStamp.h"
#include "mozilla/dom/BindContext.h" #include "mozilla/dom/BindContext.h"
@ -388,25 +389,37 @@ bool nsINode::IsSelected(const uint32_t aStartOffset,
return false; return false;
} }
nsIContent* nsINode::GetTextEditorRootContent(TextEditor** aTextEditor) { Element* nsINode::GetAnonymousRootElementOfTextEditor(
TextEditor** aTextEditor) {
if (aTextEditor) { if (aTextEditor) {
*aTextEditor = nullptr; *aTextEditor = nullptr;
} }
for (auto* element : InclusiveAncestorsOfType<nsGenericHTMLElement>()) { RefPtr<TextControlElement> textControlElement;
RefPtr<TextEditor> textEditor = element->GetTextEditorInternal(); if (IsInNativeAnonymousSubtree()) {
if (!textEditor) { textControlElement = TextControlElement::FromNodeOrNull(
continue; GetClosestNativeAnonymousSubtreeRootParent());
} } else {
textControlElement = TextControlElement::FromNode(this);
MOZ_ASSERT(!textEditor->AsHTMLEditor(),
"If it were an HTML editor, needs to use GetRootElement()");
Element* rootElement = textEditor->GetRoot();
if (aTextEditor) {
textEditor.forget(aTextEditor);
}
return rootElement;
} }
return nullptr; if (!textControlElement) {
return nullptr;
}
RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor();
if (!textEditor) {
// The found `TextControlElement` may be an input element which is not a
// text control element. In this case, such element must not be in a
// native anonymous tree of a `TextEditor` so this node is not in any
// `TextEditor`.
return nullptr;
}
MOZ_ASSERT(!textEditor->IsHTMLEditor(),
"If it were an HTML editor, needs to use GetRootElement()");
Element* rootElement = textEditor->GetRoot();
if (aTextEditor) {
textEditor.forget(aTextEditor);
}
return rootElement;
} }
nsINode* nsINode::GetRootNode(const GetRootNodeOptions& aOptions) { nsINode* nsINode::GetRootNode(const GetRootNodeOptions& aOptions) {
@ -524,8 +537,10 @@ nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell) {
if (AsContent()->HasIndependentSelection()) { if (AsContent()->HasIndependentSelection()) {
// This node should be a descendant of input/textarea editor. // This node should be a descendant of input/textarea editor.
nsIContent* content = GetTextEditorRootContent(); Element* anonymousDivElement = GetAnonymousRootElementOfTextEditor();
if (content) return content; if (anonymousDivElement) {
return anonymousDivElement;
}
} }
nsPresContext* presContext = aPresShell->GetPresContext(); nsPresContext* presContext = aPresShell->GetPresContext();

Просмотреть файл

@ -1426,11 +1426,16 @@ class nsINode : public mozilla::dom::EventTarget {
bool IsSelected(uint32_t aStartOffset, uint32_t aEndOffset) const; bool IsSelected(uint32_t aStartOffset, uint32_t aEndOffset) const;
/** /**
* Get the root content of an editor. So, this node must be a descendant of * Get the root element of the text editor associated with this node or the
* an editor. Note that this should be only used for getting input or textarea * root element of the text editor of the ancestor 'TextControlElement' if
* editor's root content. This method doesn't support HTML editors. * this is in its native anonymous subtree. I.e., this returns anonymous
* `<div>` element of a `TextEditor`. Note that this can be used only for
* getting root content of `<input>` or `<textarea>`. I.e., this method
* doesn't support HTML editors. Note that this may create a `TextEditor`
* instance, and it means that the `TextEditor` may modify its native
* anonymous subtree and may run selection listeners.
*/ */
nsIContent* GetTextEditorRootContent( MOZ_CAN_RUN_SCRIPT mozilla::dom::Element* GetAnonymousRootElementOfTextEditor(
mozilla::TextEditor** aTextEditor = nullptr); mozilla::TextEditor** aTextEditor = nullptr);
/** /**
@ -1441,7 +1446,8 @@ class nsINode : public mozilla::dom::EventTarget {
* node. Be aware that if this node and the computed selection limiter are * node. Be aware that if this node and the computed selection limiter are
* not in same subtree, this returns the root content of the closeset subtree. * not in same subtree, this returns the root content of the closeset subtree.
*/ */
nsIContent* GetSelectionRootContent(mozilla::PresShell* aPresShell); MOZ_CAN_RUN_SCRIPT_BOUNDARY nsIContent* GetSelectionRootContent(
mozilla::PresShell* aPresShell);
nsINodeList* ChildNodes(); nsINodeList* ChildNodes();