Bug 1510183 - Make HTMLEditor treat empty string attribute of style as nullptr of nsAtom rather than nsGkAtoms::_empty r=m_kato

After fixing bug 1427060, HTMLEditor treats attribute of style as nullptr.
However, if empty string is used to call NS_Atomize(), it returns
nsGkAtoms::_empty.  Therefore, HTMLEditor fails to check whether attribute is
specified or not with nullptr check since some root callers sets
nsGkAtoms::_empty instead of nullptr.

This patch makes HTMLEditor always use nullptr for empty string of attribute
of style with wrapping NS_Atomize() with AtomzieAttribute().  Additionally,
for safer change, this patch makes PropItem and TypeInState treat
nsGkAtom::_empty as nullptr.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2018-11-30 01:21:59 +00:00
Родитель 4d4e348fc8
Коммит 7348477018
6 изменённых файлов: 377 добавлений и 19 удалений

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

@ -376,6 +376,17 @@ public:
/**
* RemoveInlinePropertyAsAction() removes a property which changes inline
* style of text. E.g., bold, italic, super and sub.
*
* @param aProperty Tag name whcih represents the inline style you want to
* remove. E.g., nsGkAtoms::strong, nsGkAtoms::b, etc.
* If nsGkAtoms::href, <a> element which has href
* attribute will be removed.
* If nsGkAtoms::name, <a> element which has non-empty
* name attribute will be removed.
* @param aAttribute If aProperty is nsGkAtoms::font, aAttribute should be
* nsGkAtoms::fase, nsGkAtoms::size, nsGkAtoms::color or
* nsGkAtoms::bgcolor. Otherwise, set nullptr.
* Must not use nsGkAtoms::_empty here.
*/
nsresult RemoveInlinePropertyAsAction(nsAtom& aProperty,
nsAtom* aAttribute);

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

@ -45,6 +45,15 @@ namespace mozilla {
using namespace dom;
static already_AddRefed<nsAtom>
AtomizeAttribute(const nsAString& aAttribute)
{
if (aAttribute.IsEmpty()) {
return nullptr; // Don't use nsGkAtoms::_empty for attribute.
}
return NS_Atomize(aAttribute);
}
bool
HTMLEditor::IsEmptyTextNode(nsINode& aNode)
{
@ -97,7 +106,7 @@ HTMLEditor::SetInlineProperty(const nsAString& aProperty,
if (NS_WARN_IF(!property)) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
RefPtr<nsAtom> attribute = AtomizeAttribute(aAttribute);
AutoEditActionDataSetter editActionData(
*this,
HTMLEditUtils::GetEditActionForFormatText(*property, attribute, true));
@ -1210,7 +1219,7 @@ HTMLEditor::GetInlineProperty(const nsAString& aProperty,
bool* aAll)
{
RefPtr<nsAtom> property = NS_Atomize(aProperty);
RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
RefPtr<nsAtom> attribute = AtomizeAttribute(aAttribute);
return GetInlineProperty(property, attribute, aValue, aFirst, aAny, aAll);
}
@ -1251,7 +1260,7 @@ HTMLEditor::GetInlinePropertyWithAttrValue(const nsAString& aProperty,
nsAString& outValue)
{
RefPtr<nsAtom> property = NS_Atomize(aProperty);
RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
RefPtr<nsAtom> attribute = AtomizeAttribute(aAttribute);
return GetInlinePropertyWithAttrValue(property, attribute, aValue, aFirst,
aAny, aAll, outValue);
}
@ -1326,7 +1335,7 @@ HTMLEditor::RemoveInlineProperty(const nsAString& aProperty,
const nsAString& aAttribute)
{
RefPtr<nsAtom> property = NS_Atomize(aProperty);
RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
RefPtr<nsAtom> attribute = AtomizeAttribute(aAttribute);
AutoEditActionDataSetter editActionData(
*this,
@ -1343,6 +1352,7 @@ HTMLEditor::RemoveInlinePropertyInternal(nsAtom* aProperty,
nsAtom* aAttribute)
{
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aAttribute != nsGkAtoms::_empty);
if (NS_WARN_IF(!mRules)) {
return NS_ERROR_NOT_INITIALIZED;

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

@ -299,6 +299,9 @@ TypeInState::IsPropSet(nsAtom* aProp,
nsAString* outValue,
int32_t& outIndex)
{
if (aAttr == nsGkAtoms::_empty) {
aAttr = nullptr;
}
// linear search. list should be short.
size_t count = mSetArray.Length();
for (size_t i = 0; i < count; i++) {
@ -347,6 +350,9 @@ TypeInState::FindPropInList(nsAtom* aProp,
nsTArray<PropItem*>& aList,
int32_t& outIndex)
{
if (aAttr == nsGkAtoms::_empty) {
aAttr = nullptr;
}
// linear search. list should be short.
size_t count = aList.Length();
for (size_t i = 0; i < count; i++) {
@ -377,7 +383,7 @@ PropItem::PropItem(nsAtom* aTag,
nsAtom* aAttr,
const nsAString &aValue)
: tag(aTag)
, attr(aAttr)
, attr(aAttr != nsGkAtoms::_empty ? aAttr : nullptr)
, value(aValue)
{
MOZ_COUNT_CTOR(PropItem);

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

@ -285,6 +285,7 @@ subsuite = clipboard
skip-if = android_version == '24'
[test_nsIEditorMailSupport_insertAsCitedQuotation.html]
[test_nsIHTMLEditor_getSelectedElement.html]
[test_nsIHTMLEditor_removeInlineProperty.html]
[test_nsIHTMLEditor_selectElement.html]
[test_nsIHTMLEditor_setCaretAfterElement.html]
[test_nsIHTMLObjectResizer_hideResizers.html]

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

@ -0,0 +1,334 @@
<!DOCTYPE>
<html>
<head>
<title>Test for nsIHTMLEditor.removeInlineProperty()</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
<body>
<div id="display">
</div>
<div id="content" contenteditable></div>
<pre id="test">
</pre>
<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
let editor = document.getElementById("content");
let selection = window.getSelection();
let description, condition;
let inputEvents = [];
function onInput(aEvent) {
inputEvents.push(aEvent);
}
editor.addEventListener("input", onInput);
function checkInputEvent(aEvent, aDescription) {
if (aEvent.type != "input") {
return;
}
ok(aEvent instanceof InputEvent, `${aDescription}"input" event should be dispatched with InputEvent interface`);
is(aEvent.cancelable, false, `${aDescription}"input" event should be never cancelable`);
is(aEvent.bubbles, true, `${aDescription}"input" event should always bubble`);
}
function selectFromTextSiblings(aNode) {
condition = "selecting the node from end of previous text to start of next text node";
selection.setBaseAndExtent(aNode.previousSibling, aNode.previousSibling.length,
aNode.nextSibling, 0);
}
function selectNode(aNode) {
condition = "selecting the node";
let range = document.createRange();
range.selectNode(aNode);
selection.removeAllRanges();
selection.addRange(range);
}
function selectAllChildren(aNode) {
condition = "selecting all children of the node";
selection.selectAllChildren(aNode);
}
function selectChildContents(aNode) {
condition = "selecting all contents of its child";
let range = document.createRange();
range.selectNodeContents(aNode.firstChild);
selection.removeAllRanges();
selection.addRange(range);
}
description = "When there is a <b> element and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = "<p>test: <b>here</b> is bolden text</p>";
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("b", "");
is(editor.innerHTML, "<p>test: here is bolden text</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should remove the <b> element');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
description = "When there is a <b> element which has style attribute and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = '<p>test: <b style="font-style: italic">here</b> is bolden text</p>';
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("b", "");
is(editor.innerHTML, '<p>test: <span style="font-style: italic">here</span> is bolden text</p>',
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should replace the <b> element with <span> element to keep the style');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
description = "When there is a <b> element which has class attribute and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = '<p>test: <b class="foo">here</b> is bolden text</p>';
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("b", "");
is(editor.innerHTML, '<p>test: <span class="foo">here</span> is bolden text</p>',
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should replace the <b> element with <span> element to keep the class');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
description = "When there is a <b> element which has an <i> element and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("b", "");
is(editor.innerHTML, "<p>test: <i>here</i> is bolden and italic text</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should remove only the <b> element');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
description = "When there is a <b> element which has an <i> element and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("i", "");
is(editor.innerHTML, "<p>test: <b>here</b> is bolden and italic text</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should remove only the <i> element');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
description = "When there is an <i> element in a <b> element and ";
for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling.firstChild);
getHTMLEditor().removeInlineProperty("b", "");
is(editor.innerHTML, "<p>test: <i>here</i> is bolden and italic text</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should remove only the <b> element');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
description = "When there is an <i> element in a <b> element and ";
for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling.firstChild);
getHTMLEditor().removeInlineProperty("i", "");
is(editor.innerHTML, "<p>test: <b>here</b> is bolden and italic text</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should remove only the <i> element');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
description = "When there is an <i> element between text nodes in a <b> element and ";
for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = "<p>test: <b>h<i>e</i>re</b> is bolden and italic text</p>";
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("i", "");
is(editor.innerHTML, "<p>test: <b>here</b> is bolden and italic text</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should remove only the <i> element');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
description = "When there is an <i> element between text nodes in a <b> element and ";
for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = "<p>test: <b>h<i>e</i>re</b> is bolden and italic text</p>";
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("b", "");
is(editor.innerHTML, "<p>test: <b>h</b><i>e</i><b>re</b> is bolden and italic text</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should split the <b> element');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
description = "When there is an <a> element whose href attribute is not empty and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = '<p>test: <a href="about:blank">here</a> is a link</p>';
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("href", "");
is(editor.innerHTML, "<p>test: here is a link</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should remove the <a> element');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
// XXX In the case of "name", removeInlineProperty() does not the <a> element when name attribute is empty.
description = "When there is an <a> element whose href attribute is empty and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = '<p>test: <a href="">here</a> is a link</p>';
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("href", "");
is(editor.innerHTML, "<p>test: here is a link</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should remove the <a> element');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
description = "When there is an <a> element which does not have href attribute and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = "<p>test: <a>here</a> is an anchor</p>";
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("href", "");
is(editor.innerHTML, "<p>test: <a>here</a> is an anchor</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should NOT remove the <a> element');
is(inputEvents.length, 0,
description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should NOT cause an "input" event');
}
description = "When there is an <a> element whose name attribute is not empty and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = '<p>test: <a name="foo">here</a> is a named anchor</p>';
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("href", "");
is(editor.innerHTML, '<p>test: <a name="foo">here</a> is a named anchor</p>',
description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should NOT remove the <a> element');
is(inputEvents.length, 0,
description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should NOT cause an "input" event');
}
description = "When there is an <a> element whose name attribute is not empty and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = '<p>test: <a name="foo">here</a> is a named anchor</p>';
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("name", "");
is(editor.innerHTML, "<p>test: here is a named anchor</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should remove the <a> element');
is(inputEvents.length, 1,
description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should cause an "input" event');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], description);
}
}
// XXX In the case of "href", removeInlineProperty() removes the <a> element when href attribute is empty.
description = "When there is an <a> element whose name attribute is empty and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = '<p>test: <a name="">here</a> is a named anchor</p>';
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("name", "");
is(editor.innerHTML, '<p>test: <a name="">here</a> is a named anchor</p>',
description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT remove the <a> element');
is(inputEvents.length, 0,
description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT cause an "input" event');
}
description = "When there is an <a> element which does not have name attribute and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = "<p>test: <a>here</a> is an anchor</p>";
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("name", "");
is(editor.innerHTML, "<p>test: <a>here</a> is an anchor</p>",
description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT remove the <a> element');
is(inputEvents.length, 0,
description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT cause an "input" event');
}
description = "When there is an <a> element whose href attribute is not empty and ";
for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
editor.innerHTML = '<p>test: <a href="about:blank">here</a> is a link</p>';
editor.focus();
inputEvents = [];
prepare(editor.firstChild.firstChild.nextSibling);
getHTMLEditor().removeInlineProperty("name", "");
is(editor.innerHTML, '<p>test: <a href="about:blank">here</a> is a link</p>',
description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT remove the <a> element');
is(inputEvents.length, 0,
description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT cause an "input" event');
}
editor.removeEventListener("input", onInput);
SimpleTest.finish();
});
function getHTMLEditor() {
var Ci = SpecialPowers.Ci;
var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsIHTMLEditor);
}
</script>
</body>
</html>

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

@ -88,21 +88,17 @@ interface nsIHTMLEditor : nsISupports
/**
* removeInlineProperty() deletes the properties from all text in the current
* selection. If aProperty is not set on the selection, nothing is done.
* removeInlineProperty() removes a property which changes inline style of
* text. E.g., bold, italic, super and sub.
*
* @param aProperty the property to remove from the selection
* All atoms are for normal HTML tags (e.g.:
* nsIEditorProperty::font) except when you want to
* remove just links and not named anchors.
* For that, use nsIEditorProperty::href
* @param aAttribute the attribute of the property, if applicable.
* May be null.
* Example: aProperty=nsIEditorProptery::font,
* aAttribute="color"
* nsIEditProperty::allAttributes is special.
* It indicates that all content-based text properties
* are to be removed from the selection.
* @param aProperty Tag name whcih represents the inline style you want to
* remove. E.g., "strong", "b", etc.
* If "href", <a> element which has href attribute will be
* removed.
* If "name", <a> element which has non-empty name
* attribute will be removed.
* @param aAttribute If aProperty is "font", aAttribute should be "face",
* "size", "color" or "bgcolor".
*/
void removeInlineProperty(in AString aProperty, in AString aAttribute);