Bug 1920647 - part 1: Make HTMLEditor won't show Gecko specific editing UI in contenteditable=plaintext-only r=m_kato

Depends on D223277

Differential Revision: https://phabricator.services.mozilla.com/D223669
This commit is contained in:
Masayuki Nakano 2024-10-01 04:21:54 +00:00
parent d47496686e
commit ef73f35608
9 changed files with 177 additions and 28 deletions

View File

@ -135,6 +135,12 @@ NS_IMETHODIMP HTMLEditor::SetAbsolutePositioningEnabled(bool aIsEnabled) {
return NS_OK;
}
NS_IMETHODIMP HTMLEditor::GetIsAbsolutePositioningActive(bool* aIsActive) {
MOZ_ASSERT(aIsActive);
*aIsActive = !!mAbsolutelyPositionedObject;
return NS_OK;
}
Result<int32_t, nsresult> HTMLEditor::AddZIndexWithTransaction(
nsStyledElement& aStyledElement, int32_t aChange) {
if (!aChange) {

View File

@ -12,6 +12,7 @@
#include "mozilla/PresShellInlines.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/EventTarget.h"
#include "mozilla/mozalloc.h"
#include "nsAString.h"
@ -292,31 +293,44 @@ void HTMLEditor::HideAnonymousEditingUIs() {
void HTMLEditor::HideAnonymousEditingUIsIfUnnecessary() {
// XXX Perhaps, this is wrong approach to hide multiple UIs because
// hiding one UI may causes overwriting existing UI with newly
// created one. In such case, we will leak ovewritten UI.
if (!IsAbsolutePositionEditorEnabled() && mAbsolutelyPositionedObject) {
// XXX If we're moving something, we need to cancel or commit the
// operation now.
HideGrabberInternal();
NS_ASSERTION(!mAbsolutelyPositionedObject,
"HTMLEditor::HideGrabberInternal() failed, but ignored");
// created one. In such case, we will leak overwritten UI.
if (mAbsolutelyPositionedObject) {
const Element* const editingHost =
mAbsolutelyPositionedObject->GetEditingHost();
if (!IsAbsolutePositionEditorEnabled() || !editingHost ||
editingHost->IsContentEditablePlainTextOnly()) {
// XXX If we're moving something, we need to cancel or commit the
// operation now.
HideGrabberInternal();
NS_ASSERTION(!mAbsolutelyPositionedObject,
"HTMLEditor::HideGrabberInternal() failed, but ignored");
}
}
if (!IsInlineTableEditorEnabled() && mInlineEditedCell) {
// XXX If we're resizing a table element, we need to cancel or commit the
// operation now.
HideInlineTableEditingUIInternal();
NS_ASSERTION(
!mInlineEditedCell,
"HTMLEditor::HideInlineTableEditingUIInternal() failed, but ignored");
if (mInlineEditedCell) {
const Element* const editingHost = mInlineEditedCell->GetEditingHost();
if (!IsInlineTableEditorEnabled() || !editingHost ||
editingHost->IsContentEditablePlainTextOnly()) {
// XXX If we're resizing a table element, we need to cancel or commit the
// operation now.
HideInlineTableEditingUIInternal();
NS_ASSERTION(
!mInlineEditedCell,
"HTMLEditor::HideInlineTableEditingUIInternal() failed, but ignored");
}
}
if (!IsObjectResizerEnabled() && mResizedObject) {
// XXX If we're resizing something, we need to cancel or commit the
// operation now.
DebugOnly<nsresult> rvIgnored = HideResizersInternal();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"HTMLEditor::HideResizersInternal() failed, but ignored");
NS_ASSERTION(!mResizedObject,
"HTMLEditor::HideResizersInternal() failed, but ignored");
if (mResizedObject) {
const Element* const editingHost = mResizedObject->GetEditingHost();
if (!IsObjectResizerEnabled() || !editingHost ||
editingHost->IsContentEditablePlainTextOnly()) {
// XXX If we're resizing something, we need to cancel or commit the
// operation now.
DebugOnly<nsresult> rvIgnored = HideResizersInternal();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"HTMLEditor::HideResizersInternal() failed, but ignored");
NS_ASSERTION(!mResizedObject,
"HTMLEditor::HideResizersInternal() failed, but ignored");
}
}
}
@ -361,6 +375,13 @@ nsresult HTMLEditor::RefreshEditingUI() {
return NS_OK;
}
const RefPtr<Element> editingHost =
ComputeEditingHost(LimitInBodyElement::No);
if (editingHost && editingHost->IsContentEditablePlainTextOnly()) {
return NS_OK;
}
MOZ_ASSERT(editingHost == selectionContainerElement->GetEditingHost());
// what's its tag?
RefPtr<Element> focusElement = std::move(selectionContainerElement);
nsAtom* focusTagAtom = focusElement->NodeInfo()->NameAtom();
@ -443,11 +464,9 @@ nsresult HTMLEditor::RefreshEditingUI() {
}
// now, let's display all contextual UI for good
nsIContent* hostContent = ComputeEditingHost();
if (IsObjectResizerEnabled() && focusElement &&
HTMLEditUtils::IsSimplyEditableNode(*focusElement) &&
focusElement != hostContent) {
focusElement != editingHost) {
if (nsGkAtoms::img == focusTagAtom) {
mResizedObjectIsAnImage = true;
}
@ -468,7 +487,7 @@ nsresult HTMLEditor::RefreshEditingUI() {
if (IsAbsolutePositionEditorEnabled() && absPosElement &&
HTMLEditUtils::IsSimplyEditableNode(*absPosElement) &&
absPosElement != hostContent) {
absPosElement != editingHost) {
if (mAbsolutelyPositionedObject) {
nsresult rv = RefreshGrabberInternal();
if (NS_FAILED(rv)) {
@ -487,7 +506,7 @@ nsresult HTMLEditor::RefreshEditingUI() {
// XXX Shouldn't we check whether the `<table>` element is editable or not?
if (IsInlineTableEditorEnabled() && cellElement &&
HTMLEditUtils::IsSimplyEditableNode(*cellElement) &&
cellElement != hostContent) {
cellElement != editingHost) {
if (mInlineEditedCell) {
nsresult rv = RefreshInlineTableEditingUIInternal();
if (NS_FAILED(rv)) {

View File

@ -1481,6 +1481,12 @@ NS_IMETHODIMP HTMLEditor::SetObjectResizingEnabled(
return NS_OK;
}
NS_IMETHODIMP HTMLEditor::GetIsObjectResizingActive(bool* aIsActive) {
MOZ_ASSERT(aIsActive);
*aIsActive = !!mResizedObject;
return NS_OK;
}
#undef kTopLeft
#undef kTop
#undef kTopRight

View File

@ -31,6 +31,12 @@ NS_IMETHODIMP HTMLEditor::GetInlineTableEditingEnabled(bool* aIsEnabled) {
return NS_OK;
}
NS_IMETHODIMP HTMLEditor::GetIsInlineTableEditingActive(bool* aIsActive) {
MOZ_ASSERT(aIsActive);
*aIsActive = !!mInlineEditedCell;
return NS_OK;
}
nsresult HTMLEditor::ShowInlineTableEditingUIInternal(Element& aCellElement) {
if (NS_WARN_IF(!HTMLEditUtils::IsTableCell(&aCellElement))) {
return NS_OK;

View File

@ -444,6 +444,8 @@ skip-if = ["os == 'android'"] # Needs interaction with the scrollbar
["test_dragend_target.html"]
["test_editing_UI_in_plaintext-only.html"]
["test_execCommandPaste_noTarget.html"]
["test_focus_caret_navigation_between_nested_editors.html"]

View File

@ -0,0 +1,95 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script>
"use strict";
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(async () => {
await SpecialPowers.pushPrefEnv({
set: [["dom.element.contenteditable.plaintext-only.enabled", true]],
});
function waitForTick() {
return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
}
await (async () => {
const editingHost = document.getElementById("testAbs");
editingHost.contentEditable = "plaintext-only";
editingHost.focus();
document.execCommand("enableAbsolutePositionEditing", false, "true");
const target = editingHost.querySelector("div[style]");
synthesizeMouseAtCenter(target, {});
await waitForTick();
is(
getHTMLEditor().isAbsolutePositioningActive,
false,
"The absolutely positioned element positioner UI should not be visible"
);
document.execCommand("enableAbsolutePositionEditing", false, "false");
editingHost.remove();
await waitForTick();
})();
await (async () => {
const editingHost = document.getElementById("testTable");
editingHost.contentEditable = "plaintext-only";
editingHost.focus();
document.execCommand("enableInlineTableEditing", false, "true");
const target = editingHost.querySelector("td");
synthesizeMouseAtCenter(target, {});
await waitForTick();
is(
getHTMLEditor().isInlineTableEditingActive,
false,
"The inline table editing UI should not be visible"
);
document.execCommand("enableInlineTableEditing", false, "false");
editingHost.remove();
await waitForTick();
})();
await (async () => {
const editingHost = document.getElementById("testResizer");
editingHost.contentEditable = "plaintext-only";
editingHost.focus();
document.execCommand("enableObjectResizing", false, "true");
const target = editingHost.querySelector("img");
synthesizeMouseAtCenter(target, {});
await waitForTick();
is(
getHTMLEditor().isObjectResizingActive,
false,
"The image resizer UI should not be visible"
);
document.execCommand("enableObjectResizing", false, "false");
editingHost.remove();
await waitForTick();
})();
SimpleTest.finish();
});
function getHTMLEditor() {
const Ci = SpecialPowers.Ci;
const editingSession = SpecialPowers.wrap(window).docShell.editingSession;
return editingSession.getEditorForWindow(window)
.QueryInterface(Ci.nsIHTMLAbsPosEditor)
.QueryInterface(Ci.nsIHTMLInlineTableEditor)
.QueryInterface(Ci.nsIHTMLObjectResizer);
}
</script>
</head>
<body>
<div id="testAbs" style="position: absolute; width: 150px; height: 150px">
<div style="position: absolute; width: 100px; height: 100px"><br></div>
</div>
<div id="testTable">
<table><td><br></td></table>
</div>
<div id="testResizer">
<img src="green.png">
</div>
</body>
</html>

View File

@ -15,6 +15,11 @@ interface nsIHTMLAbsPosEditor : nsISupports
[setter_can_run_script]
attribute boolean absolutePositioningEnabled;
/**
* true if the grabber to drag the absolutly positioned element is visible.
*/
[infallible] readonly attribute boolean isAbsolutePositioningActive;
/* Utility methods */

View File

@ -17,4 +17,9 @@ interface nsIHTMLInlineTableEditor : nsISupports
*/
[setter_can_run_script]
attribute boolean inlineTableEditingEnabled;
/**
* true if the inline table editing UI is visible.
*/
[infallible] readonly attribute boolean isInlineTableEditingActive;
};

View File

@ -29,6 +29,11 @@ interface nsIHTMLObjectResizer : nsISupports
[setter_can_run_script]
attribute boolean objectResizingEnabled;
/**
* true if the object resizing UI is visible.
*/
[infallible] readonly attribute boolean isObjectResizingActive;
/**
* Hide resizers if they are visible. If this is called while there is no
* visible resizers, this does not throw exception, just does nothing.