Bug 1510183 - Make HTMLEditor treat empty string attribute of style as nullptr of nsAtom rather than nsGkAtoms::_empty r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 30 Nov 2018 01:21:59 +0000
changeset 505327 b096dc953832f7601d34625144d19c1bb43c9f7a
parent 505326 12525933eb9e4b77aed03ef536aac18a36f81278
child 505328 b7adf13336ec7d453b505cf10a7b13644fd640da
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1510183, 1427060
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
editor/libeditor/HTMLEditor.h
editor/libeditor/HTMLStyleEditor.cpp
editor/libeditor/TypeInState.cpp
editor/libeditor/tests/mochitest.ini
editor/libeditor/tests/test_nsIHTMLEditor_removeInlineProperty.html
editor/nsIHTMLEditor.idl
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -371,16 +371,27 @@ public:
                                           bool* aFirst,
                                           bool* aAny,
                                           bool* aAll,
                                           nsAString& outValue);
 
   /**
    * 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);
 
   /**
    * GetFontColorState() returns foreground color information in first
    * range of Selection.
    * If first range of Selection is collapsed and there is a cache of style for
--- a/editor/libeditor/HTMLStyleEditor.cpp
+++ b/editor/libeditor/HTMLStyleEditor.cpp
@@ -40,16 +40,25 @@
 #include "nscore.h"
 
 class nsISupports;
 
 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)
 {
   bool isEmptyTextNode = false;
   return EditorBase::IsTextNode(&aNode) &&
          NS_SUCCEEDED(IsEmptyNode(&aNode, &isEmptyTextNode)) &&
          isEmptyTextNode;
 }
@@ -92,17 +101,17 @@ NS_IMETHODIMP
 HTMLEditor::SetInlineProperty(const nsAString& aProperty,
                               const nsAString& aAttribute,
                               const nsAString& aValue)
 {
   RefPtr<nsAtom> property = NS_Atomize(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));
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   return SetInlinePropertyInternal(*property, attribute, aValue);
 }
@@ -1205,17 +1214,17 @@ NS_IMETHODIMP
 HTMLEditor::GetInlineProperty(const nsAString& aProperty,
                               const nsAString& aAttribute,
                               const nsAString& aValue,
                               bool* aFirst,
                               bool* aAny,
                               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);
 }
 
 nsresult
 HTMLEditor::GetInlineProperty(nsAtom* aProperty,
                               nsAtom* aAttribute,
                               const nsAString& aValue,
                               bool* aFirst,
@@ -1246,17 +1255,17 @@ HTMLEditor::GetInlinePropertyWithAttrVal
                                            const nsAString& aAttribute,
                                            const nsAString& aValue,
                                            bool* aFirst,
                                            bool* aAny,
                                            bool* aAll,
                                            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);
 }
 
 nsresult
 HTMLEditor::GetInlinePropertyWithAttrValue(nsAtom* aProperty,
                                            nsAtom* aAttribute,
                                            const nsAString& aValue,
@@ -1321,33 +1330,34 @@ HTMLEditor::RemoveInlinePropertyAsAction
   return NS_OK;
 }
 
 NS_IMETHODIMP
 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,
     HTMLEditUtils::GetEditActionForFormatText(*property, attribute, false));
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   return RemoveInlinePropertyInternal(property, attribute);
 }
 
 nsresult
 HTMLEditor::RemoveInlinePropertyInternal(nsAtom* aProperty,
                                          nsAtom* aAttribute)
 {
   MOZ_ASSERT(IsEditActionDataAvailable());
+  MOZ_ASSERT(aAttribute != nsGkAtoms::_empty);
 
   if (NS_WARN_IF(!mRules)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   CommitComposition();
 
   if (SelectionRefPtr()->IsCollapsed()) {
--- a/editor/libeditor/TypeInState.cpp
+++ b/editor/libeditor/TypeInState.cpp
@@ -294,16 +294,19 @@ TypeInState::IsPropSet(nsAtom* aProp,
 }
 
 bool
 TypeInState::IsPropSet(nsAtom* aProp,
                        nsAtom* aAttr,
                        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++) {
     PropItem *item = mSetArray[i];
     if (item->tag == aProp && item->attr == aAttr) {
       if (outValue) {
         *outValue = item->value;
       }
@@ -342,16 +345,19 @@ TypeInState::IsPropCleared(nsAtom* aProp
 
 bool
 TypeInState::FindPropInList(nsAtom* aProp,
                             nsAtom* aAttr,
                             nsAString* outValue,
                             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++) {
     PropItem *item = aList[i];
     if (item->tag == aProp && item->attr == aAttr) {
       if (outValue) {
         *outValue = item->value;
       }
@@ -372,17 +378,17 @@ PropItem::PropItem()
 {
   MOZ_COUNT_CTOR(PropItem);
 }
 
 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);
 }
 
 PropItem::~PropItem()
 {
   MOZ_COUNT_DTOR(PropItem);
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -280,16 +280,17 @@ skip-if = android_version == '24'
 [test_inlineTableEditing.html]
 [test_insertParagraph_in_inline_editing_host.html]
 [test_keypress_untrusted_event.html]
 [test_middle_click_paste.html]
 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]
 [test_nsIPlaintextEditor_insertLineBreak.html]
 [test_nsITableEditor_deleteTableCell.html]
 [test_nsITableEditor_deleteTableCellContents.html]
 [test_nsITableEditor_deleteTableColumn.html]
 [test_nsITableEditor_deleteTableRow.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_nsIHTMLEditor_removeInlineProperty.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>
--- a/editor/nsIHTMLEditor.idl
+++ b/editor/nsIHTMLEditor.idl
@@ -83,31 +83,27 @@ interface nsIHTMLEditor : nsISupports
   /**
    * removeAllInlineProperties() deletes all the inline properties from all
    * text in the current selection.
    */
   void removeAllInlineProperties();
 
 
   /**
-   * 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);
 
   /**
    *  Increase font size for text in selection by 1 HTML unit
    *  All existing text is scanned for existing <FONT SIZE> attributes
    *  so they will be incremented instead of inserting new <FONT> tag
    */