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:
Masayuki Nakano 2019-06-04 08:42:43 +00:00
parent 5ac5e497b5
commit d9e3ea7e57
5 changed files with 75 additions and 0 deletions

View File

@ -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();
}
}
}
//----------------------------------------------------------------------

View File

@ -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

View File

@ -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).

View 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">

View File

@ -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