mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-07 11:56:51 +00:00
Bug 1426709 - Make HTMLEditor update selection ancestor limit synchronously when editing host is changed to ancestor element r=smaug
`HTMLEditor` initializes selection ancestor limit when it receives `focus` event. If `Document.execCommand()` is called immediately after an ancestor of active editing host becomes new editing host, `HTMLEditor::GetActiveEditingHost()` returns the new one, but selection ancestor limit is still the previous one. This mismatch causes a lot of bugs. Therefore, this patch makes `nsGenericHTMLElement` notifies `HTMLEditor` of an element becoming `contenteditable`, and makes `HTMLEditor` update selection ancestor limit only when the new editing host is ancestor of old selection ancestor limit. Differential Revision: https://phabricator.services.mozilla.com/D32823 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
5ac5e497b5
commit
d9e3ea7e57
@ -10,6 +10,7 @@
|
||||
#include "mozilla/EventListenerManager.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
#include "mozilla/EventStates.h"
|
||||
#include "mozilla/HTMLEditor.h"
|
||||
#include "mozilla/MappedDeclarations.h"
|
||||
#include "mozilla/Likely.h"
|
||||
#include "mozilla/MouseEvents.h"
|
||||
@ -2450,8 +2451,10 @@ void nsGenericHTMLElement::ChangeEditableState(int32_t aChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
Document::EditingState previousEditingState = Document::EditingState::eOff;
|
||||
if (aChange != 0) {
|
||||
document->ChangeContentEditableCount(this, aChange);
|
||||
previousEditingState = document->GetEditingState();
|
||||
}
|
||||
|
||||
if (document->HasFlag(NODE_IS_EDITABLE)) {
|
||||
@ -2463,6 +2466,18 @@ void nsGenericHTMLElement::ChangeEditableState(int32_t aChange) {
|
||||
// We might as well wrap it all in one script blocker.
|
||||
nsAutoScriptBlocker scriptBlocker;
|
||||
MakeContentDescendantsEditable(this, document);
|
||||
|
||||
// If the document already had contenteditable and JS adds new
|
||||
// contenteditable, that might cause changing editing host to current editing
|
||||
// host's ancestor. In such case, HTMLEditor needs to know that
|
||||
// synchronously to update selection limitter.
|
||||
if (document && aChange > 0 &&
|
||||
previousEditingState == Document::EditingState::eContentEditable) {
|
||||
if (HTMLEditor* htmlEditor =
|
||||
nsContentUtils::GetHTMLEditor(document->GetPresContext())) {
|
||||
htmlEditor->NotifyEditingHostMaybeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
@ -4838,6 +4838,42 @@ Element* HTMLEditor::GetActiveEditingHost() const {
|
||||
return content->GetEditingHost();
|
||||
}
|
||||
|
||||
void HTMLEditor::NotifyEditingHostMaybeChanged() {
|
||||
Document* document = GetDocument();
|
||||
if (NS_WARN_IF(!document) ||
|
||||
NS_WARN_IF(document->HasFlag(NODE_IS_EDITABLE))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We're HTML editor for contenteditable
|
||||
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
|
||||
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get selection ancestor limit which may be old editing host.
|
||||
nsIContent* ancestorLimiter = SelectionRefPtr()->GetAncestorLimiter();
|
||||
if (!ancestorLimiter) {
|
||||
// If we've not initialized selection ancestor limit, we should wait focus
|
||||
// event to set proper limiter.
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute current editing host.
|
||||
nsIContent* editingHost = GetActiveEditingHost();
|
||||
if (NS_WARN_IF(!editingHost)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update selection ancestor limit if current editing host includes the
|
||||
// previous editing host.
|
||||
if (nsContentUtils::ContentIsDescendantOf(ancestorLimiter, editingHost)) {
|
||||
// Note that don't call HTMLEditor::InitializeSelectionAncestorLimit() here
|
||||
// because it may collapse selection to the first editable node.
|
||||
EditorBase::InitializeSelectionAncestorLimit(*editingHost);
|
||||
}
|
||||
}
|
||||
|
||||
EventTarget* HTMLEditor::GetDOMEventTarget() {
|
||||
// Don't use getDocument here, because we have no way of knowing
|
||||
// whether Init() was ever called. So we need to get the document
|
||||
|
@ -464,6 +464,12 @@ class HTMLEditor final : public TextEditor,
|
||||
*/
|
||||
Element* GetActiveEditingHost() const;
|
||||
|
||||
/**
|
||||
* NotifyEditingHostMaybeChanged() is called when new element becomes
|
||||
* contenteditable when the document already had contenteditable elements.
|
||||
*/
|
||||
void NotifyEditingHostMaybeChanged();
|
||||
|
||||
/** Insert a string as quoted text
|
||||
* (whose representation is dependant on the editor type),
|
||||
* replacing the selected text (if any).
|
||||
|
17
editor/libeditor/crashtests/1426709.html
Normal file
17
editor/libeditor/crashtests/1426709.html
Normal file
@ -0,0 +1,17 @@
|
||||
<script>
|
||||
function go() {
|
||||
a.setAttribute("contenteditable", "true");
|
||||
b.addEventListener("DOMNodeRemoved", eh);
|
||||
b.appendChild(c);
|
||||
}
|
||||
function eh() {
|
||||
document.body.appendChild(b);
|
||||
document.execCommand("justifyFull", false);
|
||||
document.execCommand("delete", false);
|
||||
}
|
||||
</script>
|
||||
<body onload=go()>
|
||||
<li id="a">
|
||||
A
|
||||
<pre id="b" contenteditable="true">
|
||||
<input autofocus="autofocus" id="c">
|
@ -101,6 +101,7 @@ load 1423767.html
|
||||
needs-focus load 1423776.html
|
||||
needs-focus load 1424450.html
|
||||
load 1425091.html
|
||||
load 1426709.html
|
||||
load 1441619.html
|
||||
load 1443664.html
|
||||
skip-if(Android) needs-focus load 1444630.html
|
||||
|
Loading…
Reference in New Issue
Block a user