Bug 1272623 part.3 HTMLEditRules::ReapplyCachedStyles() shouldn't set style to TypeInState if it's currently applied r?smaug
The target node of HTMLEditRules::ReapplyCachedStyles() may be styled with its parent. When HTMLEditRules::ReapplyCachedStyles() is called, it shouldn't restore another style cache if it's already specified in current DOM tree.
MozReview-Commit-ID: DKCpQ8YyW7
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -180,36 +180,42 @@ HTMLEditRules::InitFields()
mReturnInEmptyLIKillsList = true;
mDidDeleteSelection = false;
mDidRangedDelete = false;
mRestoreContentEditableCount = false;
mUtilRange = nullptr;
mJoinOffset = 0;
mNewBlock = nullptr;
mRangeItem = new RangeItem();
- // populate mCachedStyles
- mCachedStyles[0] = StyleCache(nsGkAtoms::b, EmptyString(), EmptyString());
- mCachedStyles[1] = StyleCache(nsGkAtoms::i, EmptyString(), EmptyString());
- mCachedStyles[2] = StyleCache(nsGkAtoms::u, EmptyString(), EmptyString());
- mCachedStyles[3] = StyleCache(nsGkAtoms::font, NS_LITERAL_STRING("face"), EmptyString());
- mCachedStyles[4] = StyleCache(nsGkAtoms::font, NS_LITERAL_STRING("size"), EmptyString());
- mCachedStyles[5] = StyleCache(nsGkAtoms::font, NS_LITERAL_STRING("color"), EmptyString());
- mCachedStyles[6] = StyleCache(nsGkAtoms::tt, EmptyString(), EmptyString());
- mCachedStyles[7] = StyleCache(nsGkAtoms::em, EmptyString(), EmptyString());
- mCachedStyles[8] = StyleCache(nsGkAtoms::strong, EmptyString(), EmptyString());
- mCachedStyles[9] = StyleCache(nsGkAtoms::dfn, EmptyString(), EmptyString());
- mCachedStyles[10] = StyleCache(nsGkAtoms::code, EmptyString(), EmptyString());
- mCachedStyles[11] = StyleCache(nsGkAtoms::samp, EmptyString(), EmptyString());
- mCachedStyles[12] = StyleCache(nsGkAtoms::var, EmptyString(), EmptyString());
- mCachedStyles[13] = StyleCache(nsGkAtoms::cite, EmptyString(), EmptyString());
- mCachedStyles[14] = StyleCache(nsGkAtoms::abbr, EmptyString(), EmptyString());
- mCachedStyles[15] = StyleCache(nsGkAtoms::acronym, EmptyString(), EmptyString());
- mCachedStyles[16] = StyleCache(nsGkAtoms::backgroundColor, EmptyString(), EmptyString());
- mCachedStyles[17] = StyleCache(nsGkAtoms::sub, EmptyString(), EmptyString());
- mCachedStyles[18] = StyleCache(nsGkAtoms::sup, EmptyString(), EmptyString());
+
+ InitStyleCacheArray(mCachedStyles);
+}
+
+void
+HTMLEditRules::InitStyleCacheArray(StyleCache aStyleCache[SIZE_STYLE_TABLE])
+{
+ aStyleCache[0] = StyleCache(nsGkAtoms::b, EmptyString());
+ aStyleCache[1] = StyleCache(nsGkAtoms::i, EmptyString());
+ aStyleCache[2] = StyleCache(nsGkAtoms::u, EmptyString());
+ aStyleCache[3] = StyleCache(nsGkAtoms::font, NS_LITERAL_STRING("face"));
+ aStyleCache[4] = StyleCache(nsGkAtoms::font, NS_LITERAL_STRING("size"));
+ aStyleCache[5] = StyleCache(nsGkAtoms::font, NS_LITERAL_STRING("color"));
+ aStyleCache[6] = StyleCache(nsGkAtoms::tt, EmptyString());
+ aStyleCache[7] = StyleCache(nsGkAtoms::em, EmptyString());
+ aStyleCache[8] = StyleCache(nsGkAtoms::strong, EmptyString());
+ aStyleCache[9] = StyleCache(nsGkAtoms::dfn, EmptyString());
+ aStyleCache[10] = StyleCache(nsGkAtoms::code, EmptyString());
+ aStyleCache[11] = StyleCache(nsGkAtoms::samp, EmptyString());
+ aStyleCache[12] = StyleCache(nsGkAtoms::var, EmptyString());
+ aStyleCache[13] = StyleCache(nsGkAtoms::cite, EmptyString());
+ aStyleCache[14] = StyleCache(nsGkAtoms::abbr, EmptyString());
+ aStyleCache[15] = StyleCache(nsGkAtoms::acronym, EmptyString());
+ aStyleCache[16] = StyleCache(nsGkAtoms::backgroundColor, EmptyString());
+ aStyleCache[17] = StyleCache(nsGkAtoms::sub, EmptyString());
+ aStyleCache[18] = StyleCache(nsGkAtoms::sup, EmptyString());
}
HTMLEditRules::~HTMLEditRules()
{
// remove ourselves as a listener to edit actions
// In some cases, we have already been removed by
// ~HTMLEditor, in which case we will get a null pointer here
// which we ignore. But this allows us to add the ability to
@@ -7059,48 +7065,63 @@ HTMLEditRules::GetTopEnclosingMailCite(n
}
nsresult
HTMLEditRules::CacheInlineStyles(nsIDOMNode* aNode)
{
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
NS_ENSURE_STATE(mHTMLEditor);
+
+ nsresult rv = GetInlineStyles(aNode, mCachedStyles);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::GetInlineStyles(nsIDOMNode* aNode,
+ StyleCache aStyleCache[SIZE_STYLE_TABLE])
+{
+ MOZ_ASSERT(aNode);
+ MOZ_ASSERT(mHTMLEditor);
+
bool useCSS = mHTMLEditor->IsCSSEnabled();
- for (int32_t j = 0; j < SIZE_STYLE_TABLE; ++j) {
+ for (size_t j = 0; j < SIZE_STYLE_TABLE; ++j) {
// If type-in state is set, don't intervene
bool typeInSet, unused;
if (NS_WARN_IF(!mHTMLEditor)) {
return NS_ERROR_UNEXPECTED;
}
mHTMLEditor->mTypeInState->GetTypingState(typeInSet, unused,
- mCachedStyles[j].tag, mCachedStyles[j].attr, nullptr);
+ aStyleCache[j].tag, aStyleCache[j].attr, nullptr);
if (typeInSet) {
continue;
}
bool isSet = false;
nsAutoString outValue;
// Don't use CSS for <font size>, we don't support it usefully (bug 780035)
- if (!useCSS || (mCachedStyles[j].tag == nsGkAtoms::font &&
- mCachedStyles[j].attr.EqualsLiteral("size"))) {
+ if (!useCSS || (aStyleCache[j].tag == nsGkAtoms::font &&
+ aStyleCache[j].attr.EqualsLiteral("size"))) {
NS_ENSURE_STATE(mHTMLEditor);
- mHTMLEditor->IsTextPropertySetByContent(aNode, mCachedStyles[j].tag,
- &(mCachedStyles[j].attr), nullptr,
+ mHTMLEditor->IsTextPropertySetByContent(aNode, aStyleCache[j].tag,
+ &(aStyleCache[j].attr), nullptr,
isSet, &outValue);
} else {
NS_ENSURE_STATE(mHTMLEditor);
isSet = mHTMLEditor->mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(
- aNode, mCachedStyles[j].tag, &(mCachedStyles[j].attr), outValue,
+ aNode, aStyleCache[j].tag, &(aStyleCache[j].attr), outValue,
CSSEditUtils::eComputed);
}
if (isSet) {
- mCachedStyles[j].mPresent = true;
- mCachedStyles[j].value.Assign(outValue);
+ aStyleCache[j].mPresent = true;
+ aStyleCache[j].value.Assign(outValue);
}
}
return NS_OK;
}
nsresult
HTMLEditRules::ReapplyCachedStyles()
{
@@ -7127,17 +7148,26 @@ HTMLEditRules::ReapplyCachedStyles()
}
nsCOMPtr<nsIContent> selNode =
do_QueryInterface(selection->GetRangeAt(0)->GetStartParent());
if (!selNode) {
// Nothing to do
return NS_OK;
}
- for (int32_t i = 0; i < SIZE_STYLE_TABLE; ++i) {
+ StyleCache styleAtInsertionPoint[SIZE_STYLE_TABLE];
+ InitStyleCacheArray(styleAtInsertionPoint);
+ nsCOMPtr<nsIDOMNode> selDOMNode = do_QueryInterface(selNode);
+ MOZ_ASSERT(selDOMNode);
+ nsresult rv = GetInlineStyles(selDOMNode, styleAtInsertionPoint);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_OK;
+ }
+
+ for (size_t i = 0; i < SIZE_STYLE_TABLE; ++i) {
if (mCachedStyles[i].mPresent) {
bool bFirst, bAny, bAll;
bFirst = bAny = bAll = false;
nsAutoString curValue;
if (useCSS) {
// check computed style first in css case
NS_ENSURE_STATE(mHTMLEditor);
@@ -7151,34 +7181,37 @@ HTMLEditRules::ReapplyCachedStyles()
nsresult rv =
mHTMLEditor->GetInlinePropertyBase(*mCachedStyles[i].tag,
&(mCachedStyles[i].attr),
&(mCachedStyles[i].value),
&bFirst, &bAny, &bAll,
&curValue, false);
NS_ENSURE_SUCCESS(rv, rv);
}
- // this style has disappeared through deletion. Add to our typeinstate:
- if (!bAny || IsStyleCachePreservingAction(mTheAction)) {
+ // This style has disappeared through deletion. Let's add the styles to
+ // mTypeInState when same style isn't applied to the node already.
+ if ((!bAny || IsStyleCachePreservingAction(mTheAction)) &&
+ (!styleAtInsertionPoint[i].mPresent ||
+ styleAtInsertionPoint[i].value != mCachedStyles[i].value)) {
NS_ENSURE_STATE(mHTMLEditor);
mHTMLEditor->mTypeInState->SetProp(mCachedStyles[i].tag,
mCachedStyles[i].attr,
mCachedStyles[i].value);
}
}
}
return NS_OK;
}
void
HTMLEditRules::ClearCachedStyles()
{
// clear the mPresent bits in mCachedStyles array
- for (uint32_t j = 0; j < SIZE_STYLE_TABLE; j++) {
+ for (size_t j = 0; j < SIZE_STYLE_TABLE; j++) {
mCachedStyles[j].mPresent = false;
mCachedStyles[j].value.Truncate();
}
}
void
HTMLEditRules::AdjustSpecialBreaks()
{
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -53,16 +53,24 @@ struct StyleCache final : public PropIte
const nsAString& aAttr,
const nsAString& aValue)
: PropItem(aTag, aAttr, aValue)
, mPresent(false)
{
MOZ_COUNT_CTOR(StyleCache);
}
+ StyleCache(nsIAtom* aTag,
+ const nsAString& aAttr)
+ : PropItem(aTag, aAttr, EmptyString())
+ , mPresent(false)
+ {
+ MOZ_COUNT_CTOR(StyleCache);
+ }
+
~StyleCache()
{
MOZ_COUNT_DTOR(StyleCache);
}
};
#define SIZE_STYLE_TABLE 19
@@ -400,28 +408,46 @@ protected:
nsresult MakeSureElemStartsOrEndsOnCR(nsIDOMNode* aNode, bool aStarts);
enum class ContentsOnly { no, yes };
nsresult AlignBlock(Element& aElement,
const nsAString& aAlignType, ContentsOnly aContentsOnly);
enum class Change { minus, plus };
nsresult ChangeIndentation(Element& aElement, Change aChange);
void DocumentModifiedWorker();
+ /**
+ * InitStyleCacheArray() initializes aStyleCache for usable with
+ * GetInlineStyles().
+ */
+ void InitStyleCacheArray(StyleCache aStyleCache[SIZE_STYLE_TABLE]);
+
+ /**
+ * GetInlineStyles() retrieves the style of aNode and modifies each item of
+ * aStyleCache.
+ */
+ nsresult GetInlineStyles(nsIDOMNode* aNode,
+ StyleCache aStyleCache[SIZE_STYLE_TABLE]);
+
protected:
HTMLEditor* mHTMLEditor;
RefPtr<nsRange> mDocChangeRange;
bool mListenerEnabled;
bool mReturnInEmptyLIKillsList;
bool mDidDeleteSelection;
bool mDidRangedDelete;
bool mRestoreContentEditableCount;
RefPtr<nsRange> mUtilRange;
// Need to remember an int across willJoin/didJoin...
uint32_t mJoinOffset;
nsCOMPtr<Element> mNewBlock;
RefPtr<RangeItem> mRangeItem;
+
+ // XXX In strict speaking, mCachedStyles isn't enough to cache inline styles
+ // because inline style can be specified with "style" attribute and/or
+ // CSS in <style> elements or CSS files. So, we need to look for better
+ // implementation about this.
StyleCache mCachedStyles[SIZE_STYLE_TABLE];
};
} // namespace mozilla
#endif // #ifndef HTMLEditRules_h
--- a/editor/libeditor/tests/test_inline_style_cache.html
+++ b/editor/libeditor/tests/test_inline_style_cache.html
@@ -163,116 +163,114 @@ SimpleTest.waitForFocus(function() {
synthesizeKey("t", { code: "KeyT" });
synthesizeKey("e", { code: "KeyE" });
synthesizeKey("s", { code: "KeyS" });
synthesizeKey("t", { code: "KeyT" });
is(editor.innerHTML, "<p>before</p><p><strike><i><b>test</b></i></strike>after</p>",
"#02-03-2 Typing Enter after setting style to selected text should keep the styles");
- // TODO: Following tests should pass with next patch.
-
// #03-01 Replacing in <b style="font-weight: normal;"> shouldn't cause new <b>.
editor.innerHTML = "<b style=\"font-weight: normal;\">beforeselectionafter</b>";
selection.collapse(editor.firstChild.firstChild, "before".length);
selection.extend(editor.firstChild.firstChild, "beforeselection".length);
synthesizeKey("t", { code: "KeyT" });
synthesizeKey("e", { code: "KeyE" });
synthesizeKey("s", { code: "KeyS" });
synthesizeKey("t", { code: "KeyT" });
- todo_is(editor.innerHTML, "<b style=\"font-weight: normal;\">beforetestafter</b>",
- "#03-01 Replacing text in styled inline elements should respect the styles");
+ is(editor.innerHTML, "<b style=\"font-weight: normal;\">beforetestafter</b>",
+ "#03-01 Replacing text in styled inline elements should respect the styles");
// #03-02 Typing something after removing selected text in <b style="font-weight: normal;"> shouldn't cause new <b>.
editor.innerHTML = "<b style=\"font-weight: normal;\">beforeselectionafter</b>";
selection.collapse(editor.firstChild.firstChild, "before".length);
selection.extend(editor.firstChild.firstChild, "beforeselection".length);
synthesizeKey("KEY_Backspace", { code: "Backspace" });
synthesizeKey("t", { code: "KeyT" });
synthesizeKey("e", { code: "KeyE" });
synthesizeKey("s", { code: "KeyS" });
synthesizeKey("t", { code: "KeyT" });
- todo_is(editor.innerHTML, "<b style=\"font-weight: normal;\">beforetestafter</b>",
- "#03-02 Inserting text after removing text in styled inline elements should respect the styles");
+ is(editor.innerHTML, "<b style=\"font-weight: normal;\">beforetestafter</b>",
+ "#03-02 Inserting text after removing text in styled inline elements should respect the styles");
// #03-03 Typing something after typing Enter at selected text in <b style="font-weight: normal;"> shouldn't cause new <b>.
editor.innerHTML = "<b style=\"font-weight: normal;\">beforeselectionafter</b>";
selection.collapse(editor.firstChild.firstChild, "before".length);
selection.extend(editor.firstChild.firstChild, "beforeselection".length);
synthesizeKey("KEY_Enter", { code: "Enter" });
synthesizeKey("t", { code: "KeyT" });
synthesizeKey("e", { code: "KeyE" });
synthesizeKey("s", { code: "KeyS" });
synthesizeKey("t", { code: "KeyT" });
- todo_is(editor.innerHTML, "<b style=\"font-weight: normal;\">before<br>testafter</b>",
- "#03-03-1 Inserting text after typing Enter at selected text in styled inline elements should respect the styles");
+ is(editor.innerHTML, "<b style=\"font-weight: normal;\">before<br>testafter</b>",
+ "#03-03-1 Inserting text after typing Enter at selected text in styled inline elements should respect the styles");
editor.innerHTML = "<p><b style=\"font-weight: normal;\">beforeselectionafter</b></p>";
selection.collapse(editor.firstChild.firstChild.firstChild, "before".length);
selection.extend(editor.firstChild.firstChild.firstChild, "beforeselection".length);
synthesizeKey("KEY_Enter", { code: "Enter" });
synthesizeKey("t", { code: "KeyT" });
synthesizeKey("e", { code: "KeyE" });
synthesizeKey("s", { code: "KeyS" });
synthesizeKey("t", { code: "KeyT" });
- todo_is(editor.innerHTML, "<p><b style=\"font-weight: normal;\">before</b></p><p><b style=\"font-weight: normal;\">testafter</b></p>",
- "#03-03-2 Inserting text after typing Enter at selected text in styled inline elements should respect the styles");
+ is(editor.innerHTML, "<p><b style=\"font-weight: normal;\">before</b></p><p><b style=\"font-weight: normal;\">testafter</b></p>",
+ "#03-03-2 Inserting text after typing Enter at selected text in styled inline elements should respect the styles");
// #04-01 Replacing in some styled inline elements shouldn't cause new same elements.
editor.innerHTML = "<strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">beforeselectionafter</b></i></strike>";
selection.collapse(editor.firstChild.firstChild.firstChild.firstChild, "before".length);
selection.extend(editor.firstChild.firstChild.firstChild.firstChild, "beforeselection".length);
synthesizeKey("t", { code: "KeyT" });
synthesizeKey("e", { code: "KeyE" });
synthesizeKey("s", { code: "KeyS" });
synthesizeKey("t", { code: "KeyT" });
- todo_is(editor.innerHTML, "<strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">beforetestafter</b></i></strike>",
- "#04-01 Replacing text in styled inline elements should respect the styles");
+ is(editor.innerHTML, "<strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">beforetestafter</b></i></strike>",
+ "#04-01 Replacing text in styled inline elements should respect the styles");
// #04-02 Typing something after removing selected text in some styled inline elements shouldn't cause new same elements.
editor.innerHTML = "<strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">beforeselectionafter</b>";
selection.collapse(editor.firstChild.firstChild.firstChild.firstChild, "before".length);
selection.extend(editor.firstChild.firstChild.firstChild.firstChild, "beforeselection".length);
synthesizeKey("KEY_Backspace", { code: "Backspace" });
synthesizeKey("t", { code: "KeyT" });
synthesizeKey("e", { code: "KeyE" });
synthesizeKey("s", { code: "KeyS" });
synthesizeKey("t", { code: "KeyT" });
- todo_is(editor.innerHTML, "<strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">beforetestafter</b></i></strike>",
- "#04-02 Inserting text after removing text in styled inline elements should respect the styles");
+ is(editor.innerHTML, "<strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">beforetestafter</b></i></strike>",
+ "#04-02 Inserting text after removing text in styled inline elements should respect the styles");
// #04-03 Typing something after typing Enter at selected text in some styled inline elements shouldn't cause new same elements.
editor.innerHTML = "<strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">beforeselectionafter</b>";
selection.collapse(editor.firstChild.firstChild.firstChild.firstChild, "before".length);
selection.extend(editor.firstChild.firstChild.firstChild.firstChild, "beforeselection".length);
synthesizeKey("KEY_Enter", { code: "Enter" });
synthesizeKey("t", { code: "KeyT" });
synthesizeKey("e", { code: "KeyE" });
synthesizeKey("s", { code: "KeyS" });
synthesizeKey("t", { code: "KeyT" });
- todo_is(editor.innerHTML, "<strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">before<br>testafter</b></i></strike>",
- "#04-03-1 Inserting text after typing Enter at selected text in styled inline elements should respect the styles");
+ is(editor.innerHTML, "<strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">before<br>testafter</b></i></strike>",
+ "#04-03-1 Inserting text after typing Enter at selected text in styled inline elements should respect the styles");
editor.innerHTML = "<p><strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">beforeselectionafter</b></p>";
selection.collapse(editor.firstChild.firstChild.firstChild.firstChild.firstChild, "before".length);
selection.extend(editor.firstChild.firstChild.firstChild.firstChild.firstChild, "beforeselection".length);
synthesizeKey("KEY_Enter", { code: "Enter" });
synthesizeKey("t", { code: "KeyT" });
synthesizeKey("e", { code: "KeyE" });
synthesizeKey("s", { code: "KeyS" });
synthesizeKey("t", { code: "KeyT" });
- todo_is(editor.innerHTML, "<p><strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">before</b></i></strike></p><p><strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">testafter</b></i></strike></p>",
- "#04-03-2 Inserting text after typing Enter at selected text in styled inline elements should respect the styles");
+ is(editor.innerHTML, "<p><strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">before</b></i></strike></p><p><strike style=\"text-decoration: none;\"><i style=\"font-style: normal;\"><b style=\"font-weight: normal;\">testafter</b></i></strike></p>",
+ "#04-03-2 Inserting text after typing Enter at selected text in styled inline elements should respect the styles");
SimpleTest.finish();
});
</script>
</pre>
</body>
</html>
--- a/testing/web-platform/meta/editing/run/inserttext.html.ini
+++ b/testing/web-platform/meta/editing/run/inserttext.html.ini
@@ -277,19 +277,16 @@
expected: FAIL
[[["stylewithcss","true"\],["inserttext","a"\]\] "<p>foo<span style=color:#aBcDeF>[bar</span><span style=color:#fEdCbA>baz\]</span>quz" compare innerHTML]
expected: FAIL
[[["stylewithcss","false"\],["inserttext","a"\]\] "<p>foo<span style=color:#aBcDeF>[bar</span><span style=color:#fEdCbA>baz\]</span>quz" compare innerHTML]
expected: FAIL
- [[["inserttext","a"\]\] "foo<font color=brown><a href=http://www.google.com>[bar\]</a></font>baz" compare innerHTML]
- expected: FAIL
-
[[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<b>bar\]</b>baz" compare innerHTML]
expected: FAIL
[[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<b>bar\]</b>baz" compare innerHTML]
expected: FAIL
[[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<i>bar\]</i>baz" compare innerHTML]
expected: FAIL