summaryrefslogtreecommitdiff
path: root/editor/libeditor/HTMLStyleEditor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'editor/libeditor/HTMLStyleEditor.cpp')
-rw-r--r--editor/libeditor/HTMLStyleEditor.cpp1752
1 files changed, 1752 insertions, 0 deletions
diff --git a/editor/libeditor/HTMLStyleEditor.cpp b/editor/libeditor/HTMLStyleEditor.cpp
new file mode 100644
index 0000000000..bc7141ad33
--- /dev/null
+++ b/editor/libeditor/HTMLStyleEditor.cpp
@@ -0,0 +1,1752 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/HTMLEditor.h"
+
+#include "HTMLEditUtils.h"
+#include "TextEditUtils.h"
+#include "TypeInState.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/EditorUtils.h"
+#include "mozilla/SelectionState.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/mozalloc.h"
+#include "nsAString.h"
+#include "nsAttrName.h"
+#include "nsCOMPtr.h"
+#include "nsCaseTreatment.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "nsIAtom.h"
+#include "nsIContent.h"
+#include "nsIContentIterator.h"
+#include "nsIDOMElement.h"
+#include "nsIEditor.h"
+#include "nsIEditorIMESupport.h"
+#include "nsIEditRules.h"
+#include "nsNameSpaceManager.h"
+#include "nsINode.h"
+#include "nsISupportsImpl.h"
+#include "nsLiteralString.h"
+#include "nsRange.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+#include "nscore.h"
+
+class nsISupports;
+
+namespace mozilla {
+
+using namespace dom;
+
+static bool
+IsEmptyTextNode(HTMLEditor* aThis, nsINode* aNode)
+{
+ bool isEmptyTextNode = false;
+ return EditorBase::IsTextNode(aNode) &&
+ NS_SUCCEEDED(aThis->IsEmptyNode(aNode, &isEmptyTextNode)) &&
+ isEmptyTextNode;
+}
+
+NS_IMETHODIMP
+HTMLEditor::AddDefaultProperty(nsIAtom* aProperty,
+ const nsAString& aAttribute,
+ const nsAString& aValue)
+{
+ nsString outValue;
+ int32_t index;
+ nsString attr(aAttribute);
+ if (TypeInState::FindPropInList(aProperty, attr, &outValue,
+ mDefaultStyles, index)) {
+ PropItem *item = mDefaultStyles[index];
+ item->value = aValue;
+ } else {
+ nsString value(aValue);
+ PropItem *propItem = new PropItem(aProperty, attr, value);
+ mDefaultStyles.AppendElement(propItem);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::RemoveDefaultProperty(nsIAtom* aProperty,
+ const nsAString& aAttribute,
+ const nsAString& aValue)
+{
+ nsString outValue;
+ int32_t index;
+ nsString attr(aAttribute);
+ if (TypeInState::FindPropInList(aProperty, attr, &outValue,
+ mDefaultStyles, index)) {
+ delete mDefaultStyles[index];
+ mDefaultStyles.RemoveElementAt(index);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::RemoveAllDefaultProperties()
+{
+ size_t defcon = mDefaultStyles.Length();
+ for (size_t j = 0; j < defcon; j++) {
+ delete mDefaultStyles[j];
+ }
+ mDefaultStyles.Clear();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+HTMLEditor::SetInlineProperty(nsIAtom* aProperty,
+ const nsAString& aAttribute,
+ const nsAString& aValue)
+{
+ NS_ENSURE_TRUE(aProperty, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
+ nsCOMPtr<nsIEditRules> rules(mRules);
+ ForceCompositionEnd();
+
+ RefPtr<Selection> selection = GetSelection();
+ NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
+
+ if (selection->Collapsed()) {
+ // Manipulating text attributes on a collapsed selection only sets state
+ // for the next text insertion
+ mTypeInState->SetProp(aProperty, aAttribute, aValue);
+ return NS_OK;
+ }
+
+ AutoEditBatch batchIt(this);
+ AutoRules beginRulesSniffing(this, EditAction::insertElement,
+ nsIEditor::eNext);
+ AutoSelectionRestorer selectionRestorer(selection, this);
+ AutoTransactionsConserveSelection dontSpazMySelection(this);
+
+ bool cancel, handled;
+ TextRulesInfo ruleInfo(EditAction::setTextProperty);
+ // Protect the edit rules object from dying
+ nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!cancel && !handled) {
+ // Loop through the ranges in the selection
+ uint32_t rangeCount = selection->RangeCount();
+ for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; rangeIdx++) {
+ RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
+
+ // Adjust range to include any ancestors whose children are entirely
+ // selected
+ rv = PromoteInlineRange(*range);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check for easy case: both range endpoints in same text node
+ nsCOMPtr<nsINode> startNode = range->GetStartParent();
+ nsCOMPtr<nsINode> endNode = range->GetEndParent();
+ if (startNode && startNode == endNode && startNode->GetAsText()) {
+ rv = SetInlinePropertyOnTextNode(*startNode->GetAsText(),
+ range->StartOffset(),
+ range->EndOffset(),
+ *aProperty, &aAttribute, aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+
+ // Not the easy case. Range not contained in single text node. There
+ // are up to three phases here. There are all the nodes reported by the
+ // subtree iterator to be processed. And there are potentially a
+ // starting textnode and an ending textnode which are only partially
+ // contained by the range.
+
+ // Let's handle the nodes reported by the iterator. These nodes are
+ // entirely contained in the selection range. We build up a list of them
+ // (since doing operations on the document during iteration would perturb
+ // the iterator).
+
+ OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
+
+ nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
+
+ // Iterate range and build up array
+ rv = iter->Init(range);
+ // Init returns an error if there are no nodes in range. This can easily
+ // happen with the subtree iterator if the selection doesn't contain any
+ // *whole* nodes.
+ if (NS_SUCCEEDED(rv)) {
+ for (; !iter->IsDone(); iter->Next()) {
+ OwningNonNull<nsINode> node = *iter->GetCurrentNode();
+
+ if (node->IsContent() && IsEditable(node)) {
+ arrayOfNodes.AppendElement(*node->AsContent());
+ }
+ }
+ }
+ // First check the start parent of the range to see if it needs to be
+ // separately handled (it does if it's a text node, due to how the
+ // subtree iterator works - it will not have reported it).
+ if (startNode && startNode->GetAsText() && IsEditable(startNode)) {
+ rv = SetInlinePropertyOnTextNode(*startNode->GetAsText(),
+ range->StartOffset(),
+ startNode->Length(), *aProperty,
+ &aAttribute, aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Then loop through the list, set the property on each node
+ for (auto& node : arrayOfNodes) {
+ rv = SetInlinePropertyOnNode(*node, *aProperty, &aAttribute, aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Last check the end parent of the range to see if it needs to be
+ // separately handled (it does if it's a text node, due to how the
+ // subtree iterator works - it will not have reported it).
+ if (endNode && endNode->GetAsText() && IsEditable(endNode)) {
+ rv = SetInlinePropertyOnTextNode(*endNode->GetAsText(), 0,
+ range->EndOffset(), *aProperty,
+ &aAttribute, aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ if (!cancel) {
+ // Post-process
+ return rules->DidDoAction(selection, &ruleInfo, rv);
+ }
+ return NS_OK;
+}
+
+// Helper function for SetInlinePropertyOn*: is aNode a simple old <b>, <font>,
+// <span style="">, etc. that we can reuse instead of creating a new one?
+bool
+HTMLEditor::IsSimpleModifiableNode(nsIContent* aContent,
+ nsIAtom* aProperty,
+ const nsAString* aAttribute,
+ const nsAString* aValue)
+{
+ // aContent can be null, in which case we'll return false in a few lines
+ MOZ_ASSERT(aProperty);
+ MOZ_ASSERT_IF(aAttribute, aValue);
+
+ nsCOMPtr<dom::Element> element = do_QueryInterface(aContent);
+ if (!element) {
+ return false;
+ }
+
+ // First check for <b>, <i>, etc.
+ if (element->IsHTMLElement(aProperty) && !element->GetAttrCount() &&
+ (!aAttribute || aAttribute->IsEmpty())) {
+ return true;
+ }
+
+ // Special cases for various equivalencies: <strong>, <em>, <s>
+ if (!element->GetAttrCount() &&
+ ((aProperty == nsGkAtoms::b &&
+ element->IsHTMLElement(nsGkAtoms::strong)) ||
+ (aProperty == nsGkAtoms::i &&
+ element->IsHTMLElement(nsGkAtoms::em)) ||
+ (aProperty == nsGkAtoms::strike &&
+ element->IsHTMLElement(nsGkAtoms::s)))) {
+ return true;
+ }
+
+ // Now look for things like <font>
+ if (aAttribute && !aAttribute->IsEmpty()) {
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(*aAttribute);
+ MOZ_ASSERT(atom);
+
+ nsString attrValue;
+ if (element->IsHTMLElement(aProperty) &&
+ IsOnlyAttribute(element, *aAttribute) &&
+ element->GetAttr(kNameSpaceID_None, atom, attrValue) &&
+ attrValue.Equals(*aValue, nsCaseInsensitiveStringComparator())) {
+ // This is not quite correct, because it excludes cases like
+ // <font face=000> being the same as <font face=#000000>.
+ // Property-specific handling is needed (bug 760211).
+ return true;
+ }
+ }
+
+ // No luck so far. Now we check for a <span> with a single style=""
+ // attribute that sets only the style we're looking for, if this type of
+ // style supports it
+ if (!mCSSEditUtils->IsCSSEditableProperty(element, aProperty, aAttribute) ||
+ !element->IsHTMLElement(nsGkAtoms::span) ||
+ element->GetAttrCount() != 1 ||
+ !element->HasAttr(kNameSpaceID_None, nsGkAtoms::style)) {
+ return false;
+ }
+
+ // Some CSS styles are not so simple. For instance, underline is
+ // "text-decoration: underline", which decomposes into four different text-*
+ // properties. So for now, we just create a span, add the desired style, and
+ // see if it matches.
+ nsCOMPtr<Element> newSpan = CreateHTMLContent(nsGkAtoms::span);
+ NS_ASSERTION(newSpan, "CreateHTMLContent failed");
+ NS_ENSURE_TRUE(newSpan, false);
+ mCSSEditUtils->SetCSSEquivalentToHTMLStyle(newSpan, aProperty,
+ aAttribute, aValue,
+ /*suppress transaction*/ true);
+
+ return mCSSEditUtils->ElementsSameStyle(newSpan, element);
+}
+
+nsresult
+HTMLEditor::SetInlinePropertyOnTextNode(Text& aText,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsIAtom& aProperty,
+ const nsAString* aAttribute,
+ const nsAString& aValue)
+{
+ if (!aText.GetParentNode() ||
+ !CanContainTag(*aText.GetParentNode(), aProperty)) {
+ return NS_OK;
+ }
+
+ // Don't need to do anything if no characters actually selected
+ if (aStartOffset == aEndOffset) {
+ return NS_OK;
+ }
+
+ // Don't need to do anything if property already set on node
+ if (mCSSEditUtils->IsCSSEditableProperty(&aText, &aProperty, aAttribute)) {
+ // The HTML styles defined by aProperty/aAttribute have a CSS equivalence
+ // for node; let's check if it carries those CSS styles
+ if (mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(&aText, &aProperty,
+ aAttribute, aValue, CSSEditUtils::eComputed)) {
+ return NS_OK;
+ }
+ } else if (IsTextPropertySetByContent(&aText, &aProperty, aAttribute,
+ &aValue)) {
+ return NS_OK;
+ }
+
+ // Do we need to split the text node?
+ ErrorResult rv;
+ RefPtr<Text> text = &aText;
+ if (uint32_t(aEndOffset) != aText.Length()) {
+ // We need to split off back of text node
+ text = SplitNode(aText, aEndOffset, rv)->GetAsText();
+ NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
+ }
+
+ if (aStartOffset) {
+ // We need to split off front of text node
+ SplitNode(*text, aStartOffset, rv);
+ NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
+ }
+
+ if (aAttribute) {
+ // Look for siblings that are correct type of node
+ nsIContent* sibling = GetPriorHTMLSibling(text);
+ if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
+ // Previous sib is already right kind of inline node; slide this over
+ return MoveNode(text, sibling, -1);
+ }
+ sibling = GetNextHTMLSibling(text);
+ if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
+ // Following sib is already right kind of inline node; slide this over
+ return MoveNode(text, sibling, 0);
+ }
+ }
+
+ // Reparent the node inside inline node with appropriate {attribute,value}
+ return SetInlinePropertyOnNode(*text, aProperty, aAttribute, aValue);
+}
+
+nsresult
+HTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent& aNode,
+ nsIAtom& aProperty,
+ const nsAString* aAttribute,
+ const nsAString& aValue)
+{
+ nsCOMPtr<nsIAtom> attrAtom = aAttribute ? NS_Atomize(*aAttribute) : nullptr;
+
+ // If this is an element that can't be contained in a span, we have to
+ // recurse to its children.
+ if (!TagCanContain(*nsGkAtoms::span, aNode)) {
+ if (aNode.HasChildren()) {
+ nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
+
+ // Populate the list.
+ for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (IsEditable(child) && !IsEmptyTextNode(this, child)) {
+ arrayOfNodes.AppendElement(*child);
+ }
+ }
+
+ // Then loop through the list, set the property on each node.
+ for (auto& node : arrayOfNodes) {
+ nsresult rv = SetInlinePropertyOnNode(node, aProperty, aAttribute,
+ aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+ }
+
+ // First check if there's an adjacent sibling we can put our node into.
+ nsCOMPtr<nsIContent> previousSibling = GetPriorHTMLSibling(&aNode);
+ nsCOMPtr<nsIContent> nextSibling = GetNextHTMLSibling(&aNode);
+ if (IsSimpleModifiableNode(previousSibling, &aProperty, aAttribute, &aValue)) {
+ nsresult rv = MoveNode(&aNode, previousSibling, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (IsSimpleModifiableNode(nextSibling, &aProperty, aAttribute, &aValue)) {
+ rv = JoinNodes(*previousSibling, *nextSibling);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+ }
+ if (IsSimpleModifiableNode(nextSibling, &aProperty, aAttribute, &aValue)) {
+ nsresult rv = MoveNode(&aNode, nextSibling, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ // Don't need to do anything if property already set on node
+ if (mCSSEditUtils->IsCSSEditableProperty(&aNode, &aProperty, aAttribute)) {
+ if (mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(
+ &aNode, &aProperty, aAttribute, aValue, CSSEditUtils::eComputed)) {
+ return NS_OK;
+ }
+ } else if (IsTextPropertySetByContent(&aNode, &aProperty,
+ aAttribute, &aValue)) {
+ return NS_OK;
+ }
+
+ bool useCSS = (IsCSSEnabled() &&
+ mCSSEditUtils->IsCSSEditableProperty(&aNode, &aProperty,
+ aAttribute)) ||
+ // bgcolor is always done using CSS
+ aAttribute->EqualsLiteral("bgcolor");
+
+ if (useCSS) {
+ nsCOMPtr<dom::Element> tmp;
+ // We only add style="" to <span>s with no attributes (bug 746515). If we
+ // don't have one, we need to make one.
+ if (aNode.IsHTMLElement(nsGkAtoms::span) &&
+ !aNode.AsElement()->GetAttrCount()) {
+ tmp = aNode.AsElement();
+ } else {
+ tmp = InsertContainerAbove(&aNode, nsGkAtoms::span);
+ NS_ENSURE_STATE(tmp);
+ }
+
+ // Add the CSS styles corresponding to the HTML style request
+ int32_t count;
+ nsresult rv =
+ mCSSEditUtils->SetCSSEquivalentToHTMLStyle(tmp->AsDOMNode(),
+ &aProperty, aAttribute,
+ &aValue, &count, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ // is it already the right kind of node, but with wrong attribute?
+ if (aNode.IsHTMLElement(&aProperty)) {
+ // Just set the attribute on it.
+ nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(&aNode);
+ return SetAttribute(elem, *aAttribute, aValue);
+ }
+
+ // ok, chuck it in its very own container
+ nsCOMPtr<Element> tmp = InsertContainerAbove(&aNode, &aProperty, attrAtom,
+ &aValue);
+ NS_ENSURE_STATE(tmp);
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditor::SetInlinePropertyOnNode(nsIContent& aNode,
+ nsIAtom& aProperty,
+ const nsAString* aAttribute,
+ const nsAString& aValue)
+{
+ nsCOMPtr<nsIContent> previousSibling = aNode.GetPreviousSibling(),
+ nextSibling = aNode.GetNextSibling();
+ NS_ENSURE_STATE(aNode.GetParentNode());
+ OwningNonNull<nsINode> parent = *aNode.GetParentNode();
+
+ nsresult rv = RemoveStyleInside(aNode, &aProperty, aAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNode.GetParentNode()) {
+ // The node is still where it was
+ return SetInlinePropertyOnNodeImpl(aNode, aProperty,
+ aAttribute, aValue);
+ }
+
+ // It's vanished. Use the old siblings for reference to construct a
+ // list. But first, verify that the previous/next siblings are still
+ // where we expect them; otherwise we have to give up.
+ if ((previousSibling && previousSibling->GetParentNode() != parent) ||
+ (nextSibling && nextSibling->GetParentNode() != parent)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsTArray<OwningNonNull<nsIContent>> nodesToSet;
+ nsCOMPtr<nsIContent> cur = previousSibling
+ ? previousSibling->GetNextSibling() : parent->GetFirstChild();
+ for (; cur && cur != nextSibling; cur = cur->GetNextSibling()) {
+ if (IsEditable(cur)) {
+ nodesToSet.AppendElement(*cur);
+ }
+ }
+
+ for (auto& node : nodesToSet) {
+ rv = SetInlinePropertyOnNodeImpl(node, aProperty, aAttribute, aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditor::SplitStyleAboveRange(nsRange* inRange,
+ nsIAtom* aProperty,
+ const nsAString* aAttribute)
+{
+ NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
+
+ nsCOMPtr<nsINode> startNode = inRange->GetStartParent();
+ int32_t startOffset = inRange->StartOffset();
+ nsCOMPtr<nsINode> endNode = inRange->GetEndParent();
+ int32_t endOffset = inRange->EndOffset();
+
+ nsCOMPtr<nsINode> origStartNode = startNode;
+
+ // split any matching style nodes above the start of range
+ {
+ AutoTrackDOMPoint tracker(mRangeUpdater, address_of(endNode), &endOffset);
+ nsresult rv =
+ SplitStyleAbovePoint(address_of(startNode), &startOffset, aProperty,
+ aAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // second verse, same as the first...
+ nsresult rv =
+ SplitStyleAbovePoint(address_of(endNode), &endOffset, aProperty,
+ aAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // reset the range
+ rv = inRange->SetStart(startNode, startOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return inRange->SetEnd(endNode, endOffset);
+}
+
+nsresult
+HTMLEditor::SplitStyleAbovePoint(nsCOMPtr<nsINode>* aNode,
+ int32_t* aOffset,
+ // null here means we split all properties
+ nsIAtom* aProperty,
+ const nsAString* aAttribute,
+ nsIContent** aOutLeftNode,
+ nsIContent** aOutRightNode)
+{
+ NS_ENSURE_TRUE(aNode && *aNode && aOffset, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE((*aNode)->IsContent(), NS_OK);
+
+ // Split any matching style nodes above the node/offset
+ OwningNonNull<nsIContent> node = *(*aNode)->AsContent();
+
+ bool useCSS = IsCSSEnabled();
+
+ bool isSet;
+ while (!IsBlockNode(node) && node->GetParent() &&
+ IsEditable(node->GetParent())) {
+ isSet = false;
+ if (useCSS && mCSSEditUtils->IsCSSEditableProperty(node, aProperty,
+ aAttribute)) {
+ // The HTML style defined by aProperty/aAttribute has a CSS equivalence
+ // in this implementation for the node; let's check if it carries those
+ // CSS styles
+ nsAutoString firstValue;
+ mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(GetAsDOMNode(node),
+ aProperty, aAttribute, isSet, firstValue, CSSEditUtils::eSpecified);
+ }
+ if (// node is the correct inline prop
+ (aProperty && node->IsHTMLElement(aProperty)) ||
+ // node is href - test if really <a href=...
+ (aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(node)) ||
+ // or node is any prop, and we asked to split them all
+ (!aProperty && NodeIsProperty(node)) ||
+ // or the style is specified in the style attribute
+ isSet) {
+ // Found a style node we need to split
+ int32_t offset = SplitNodeDeep(*node, *(*aNode)->AsContent(), *aOffset,
+ EmptyContainers::yes, aOutLeftNode,
+ aOutRightNode);
+ NS_ENSURE_TRUE(offset != -1, NS_ERROR_FAILURE);
+ // reset startNode/startOffset
+ *aNode = node->GetParent();
+ *aOffset = offset;
+ }
+ node = node->GetParent();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditor::ClearStyle(nsCOMPtr<nsINode>* aNode,
+ int32_t* aOffset,
+ nsIAtom* aProperty,
+ const nsAString* aAttribute)
+{
+ nsCOMPtr<nsIContent> leftNode, rightNode;
+ nsresult rv = SplitStyleAbovePoint(aNode, aOffset, aProperty,
+ aAttribute, getter_AddRefs(leftNode),
+ getter_AddRefs(rightNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (leftNode) {
+ bool bIsEmptyNode;
+ IsEmptyNode(leftNode, &bIsEmptyNode, false, true);
+ if (bIsEmptyNode) {
+ // delete leftNode if it became empty
+ rv = DeleteNode(leftNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ if (rightNode) {
+ nsCOMPtr<nsINode> secondSplitParent = GetLeftmostChild(rightNode);
+ // don't try to split non-containers (br's, images, hr's, etc.)
+ if (!secondSplitParent) {
+ secondSplitParent = rightNode;
+ }
+ nsCOMPtr<Element> savedBR;
+ if (!IsContainer(secondSplitParent)) {
+ if (TextEditUtils::IsBreak(secondSplitParent)) {
+ savedBR = do_QueryInterface(secondSplitParent);
+ NS_ENSURE_STATE(savedBR);
+ }
+
+ secondSplitParent = secondSplitParent->GetParentNode();
+ }
+ *aOffset = 0;
+ rv = SplitStyleAbovePoint(address_of(secondSplitParent),
+ aOffset, aProperty, aAttribute,
+ getter_AddRefs(leftNode),
+ getter_AddRefs(rightNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (rightNode) {
+ bool bIsEmptyNode;
+ IsEmptyNode(rightNode, &bIsEmptyNode, false, true);
+ if (bIsEmptyNode) {
+ // delete rightNode if it became empty
+ rv = DeleteNode(rightNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ if (!leftNode) {
+ return NS_OK;
+ }
+
+ // should be impossible to not get a new leftnode here
+ nsCOMPtr<nsINode> newSelParent = GetLeftmostChild(leftNode);
+ if (!newSelParent) {
+ newSelParent = leftNode;
+ }
+ // If rightNode starts with a br, suck it out of right node and into
+ // leftNode. This is so we you don't revert back to the previous style
+ // if you happen to click at the end of a line.
+ if (savedBR) {
+ rv = MoveNode(savedBR, newSelParent, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // remove the style on this new hierarchy
+ int32_t newSelOffset = 0;
+ {
+ // Track the point at the new hierarchy. This is so we can know where
+ // to put the selection after we call RemoveStyleInside().
+ // RemoveStyleInside() could remove any and all of those nodes, so I
+ // have to use the range tracking system to find the right spot to put
+ // selection.
+ AutoTrackDOMPoint tracker(mRangeUpdater,
+ address_of(newSelParent), &newSelOffset);
+ rv = RemoveStyleInside(*leftNode, aProperty, aAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // reset our node offset values to the resulting new sel point
+ *aNode = newSelParent;
+ *aOffset = newSelOffset;
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLEditor::NodeIsProperty(nsINode& aNode)
+{
+ return IsContainer(&aNode) && IsEditable(&aNode) && !IsBlockNode(&aNode) &&
+ !aNode.IsHTMLElement(nsGkAtoms::a);
+}
+
+nsresult
+HTMLEditor::ApplyDefaultProperties()
+{
+ size_t defcon = mDefaultStyles.Length();
+ for (size_t j = 0; j < defcon; j++) {
+ PropItem *propItem = mDefaultStyles[j];
+ NS_ENSURE_TRUE(propItem, NS_ERROR_NULL_POINTER);
+ nsresult rv =
+ SetInlineProperty(propItem->tag, propItem->attr, propItem->value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditor::RemoveStyleInside(nsIContent& aNode,
+ nsIAtom* aProperty,
+ const nsAString* aAttribute,
+ const bool aChildrenOnly /* = false */)
+{
+ if (aNode.GetAsText()) {
+ return NS_OK;
+ }
+
+ // first process the children
+ RefPtr<nsIContent> child = aNode.GetFirstChild();
+ while (child) {
+ // cache next sibling since we might remove child
+ nsCOMPtr<nsIContent> next = child->GetNextSibling();
+ nsresult rv = RemoveStyleInside(*child, aProperty, aAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ child = next.forget();
+ }
+
+ // then process the node itself
+ if (!aChildrenOnly &&
+ // node is prop we asked for
+ ((aProperty && aNode.NodeInfo()->NameAtom() == aProperty) ||
+ // but check for link (<a href=...)
+ (aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(&aNode)) ||
+ // and for named anchors
+ (aProperty == nsGkAtoms::name && HTMLEditUtils::IsNamedAnchor(&aNode)) ||
+ // or node is any prop and we asked for that
+ (!aProperty && NodeIsProperty(aNode)))) {
+ // if we weren't passed an attribute, then we want to
+ // remove any matching inlinestyles entirely
+ if (!aAttribute || aAttribute->IsEmpty()) {
+ NS_NAMED_LITERAL_STRING(styleAttr, "style");
+ NS_NAMED_LITERAL_STRING(classAttr, "class");
+
+ bool hasStyleAttr = aNode.HasAttr(kNameSpaceID_None, nsGkAtoms::style);
+ bool hasClassAttr = aNode.HasAttr(kNameSpaceID_None, nsGkAtoms::_class);
+ if (aProperty && (hasStyleAttr || hasClassAttr)) {
+ // aNode carries inline styles or a class attribute so we can't
+ // just remove the element... We need to create above the element
+ // a span that will carry those styles or class, then we can delete
+ // the node.
+ nsCOMPtr<Element> spanNode =
+ InsertContainerAbove(&aNode, nsGkAtoms::span);
+ NS_ENSURE_STATE(spanNode);
+ nsresult rv =
+ CloneAttribute(styleAttr, spanNode->AsDOMNode(), aNode.AsDOMNode());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv =
+ CloneAttribute(classAttr, spanNode->AsDOMNode(), aNode.AsDOMNode());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsresult rv = RemoveContainer(&aNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // otherwise we just want to eliminate the attribute
+ nsCOMPtr<nsIAtom> attribute = NS_Atomize(*aAttribute);
+ if (aNode.HasAttr(kNameSpaceID_None, attribute)) {
+ // if this matching attribute is the ONLY one on the node,
+ // then remove the whole node. Otherwise just nix the attribute.
+ if (IsOnlyAttribute(&aNode, *aAttribute)) {
+ nsresult rv = RemoveContainer(&aNode);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(&aNode);
+ NS_ENSURE_TRUE(elem, NS_ERROR_NULL_POINTER);
+ nsresult rv = RemoveAttribute(elem, *aAttribute);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+ }
+ }
+
+ if (!aChildrenOnly &&
+ mCSSEditUtils->IsCSSEditableProperty(&aNode, aProperty, aAttribute)) {
+ // the HTML style defined by aProperty/aAttribute has a CSS equivalence in
+ // this implementation for the node aNode; let's check if it carries those
+ // css styles
+ nsAutoString propertyValue;
+ bool isSet = mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(&aNode,
+ aProperty, aAttribute, propertyValue, CSSEditUtils::eSpecified);
+ if (isSet && aNode.IsElement()) {
+ // yes, tmp has the corresponding css declarations in its style attribute
+ // let's remove them
+ mCSSEditUtils->RemoveCSSEquivalentToHTMLStyle(aNode.AsElement(),
+ aProperty,
+ aAttribute,
+ &propertyValue,
+ false);
+ // remove the node if it is a span or font, if its style attribute is
+ // empty or absent, and if it does not have a class nor an id
+ RemoveElementIfNoStyleOrIdOrClass(*aNode.AsElement());
+ }
+ }
+
+ // Or node is big or small and we are setting font size
+ if (aChildrenOnly) {
+ return NS_OK;
+ }
+ if (aProperty == nsGkAtoms::font &&
+ (aNode.IsHTMLElement(nsGkAtoms::big) ||
+ aNode.IsHTMLElement(nsGkAtoms::small)) &&
+ aAttribute && aAttribute->LowerCaseEqualsLiteral("size")) {
+ // if we are setting font size, remove any nested bigs and smalls
+ return RemoveContainer(&aNode);
+ }
+ return NS_OK;
+}
+
+bool
+HTMLEditor::IsOnlyAttribute(const nsIContent* aContent,
+ const nsAString& aAttribute)
+{
+ MOZ_ASSERT(aContent);
+
+ uint32_t attrCount = aContent->GetAttrCount();
+ for (uint32_t i = 0; i < attrCount; ++i) {
+ const nsAttrName* name = aContent->GetAttrNameAt(i);
+ if (!name->NamespaceEquals(kNameSpaceID_None)) {
+ return false;
+ }
+
+ nsAutoString attrString;
+ name->LocalName()->ToString(attrString);
+ // if it's the attribute we know about, or a special _moz attribute,
+ // keep looking
+ if (!attrString.Equals(aAttribute, nsCaseInsensitiveStringComparator()) &&
+ !StringBeginsWith(attrString, NS_LITERAL_STRING("_moz"))) {
+ return false;
+ }
+ }
+ // if we made it through all of them without finding a real attribute
+ // other than aAttribute, then return true
+ return true;
+}
+
+nsresult
+HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange& aRange)
+{
+ // We assume that <a> is not nested.
+ nsCOMPtr<nsINode> startNode = aRange.GetStartParent();
+ int32_t startOffset = aRange.StartOffset();
+ nsCOMPtr<nsINode> endNode = aRange.GetEndParent();
+ int32_t endOffset = aRange.EndOffset();
+
+ nsCOMPtr<nsINode> parent = startNode;
+
+ while (parent && !parent->IsHTMLElement(nsGkAtoms::body) &&
+ !HTMLEditUtils::IsNamedAnchor(parent)) {
+ parent = parent->GetParentNode();
+ }
+ NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
+
+ if (HTMLEditUtils::IsNamedAnchor(parent)) {
+ startNode = parent->GetParentNode();
+ startOffset = startNode ? startNode->IndexOf(parent) : -1;
+ }
+
+ parent = endNode;
+ while (parent && !parent->IsHTMLElement(nsGkAtoms::body) &&
+ !HTMLEditUtils::IsNamedAnchor(parent)) {
+ parent = parent->GetParentNode();
+ }
+ NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
+
+ if (HTMLEditUtils::IsNamedAnchor(parent)) {
+ endNode = parent->GetParentNode();
+ endOffset = endNode ? endNode->IndexOf(parent) + 1 : 0;
+ }
+
+ nsresult rv = aRange.SetStart(startNode, startOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aRange.SetEnd(endNode, endOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditor::PromoteInlineRange(nsRange& aRange)
+{
+ nsCOMPtr<nsINode> startNode = aRange.GetStartParent();
+ int32_t startOffset = aRange.StartOffset();
+ nsCOMPtr<nsINode> endNode = aRange.GetEndParent();
+ int32_t endOffset = aRange.EndOffset();
+
+ while (startNode && !startNode->IsHTMLElement(nsGkAtoms::body) &&
+ IsEditable(startNode) && IsAtFrontOfNode(*startNode, startOffset)) {
+ nsCOMPtr<nsINode> parent = startNode->GetParentNode();
+ NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
+ startOffset = parent->IndexOf(startNode);
+ startNode = parent;
+ }
+
+ while (endNode && !endNode->IsHTMLElement(nsGkAtoms::body) &&
+ IsEditable(endNode) && IsAtEndOfNode(*endNode, endOffset)) {
+ nsCOMPtr<nsINode> parent = endNode->GetParentNode();
+ NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
+ // We are AFTER this node
+ endOffset = 1 + parent->IndexOf(endNode);
+ endNode = parent;
+ }
+
+ nsresult rv = aRange.SetStart(startNode, startOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aRange.SetEnd(endNode, endOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+bool
+HTMLEditor::IsAtFrontOfNode(nsINode& aNode,
+ int32_t aOffset)
+{
+ if (!aOffset) {
+ return true;
+ }
+
+ if (IsTextNode(&aNode)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIContent> firstNode = GetFirstEditableChild(aNode);
+ NS_ENSURE_TRUE(firstNode, true);
+ if (aNode.IndexOf(firstNode) < aOffset) {
+ return false;
+ }
+ return true;
+}
+
+bool
+HTMLEditor::IsAtEndOfNode(nsINode& aNode,
+ int32_t aOffset)
+{
+ if (aOffset == (int32_t)aNode.Length()) {
+ return true;
+ }
+
+ if (IsTextNode(&aNode)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIContent> lastNode = GetLastEditableChild(aNode);
+ NS_ENSURE_TRUE(lastNode, true);
+ if (aNode.IndexOf(lastNode) < aOffset) {
+ return true;
+ }
+ return false;
+}
+
+
+nsresult
+HTMLEditor::GetInlinePropertyBase(nsIAtom& aProperty,
+ const nsAString* aAttribute,
+ const nsAString* aValue,
+ bool* aFirst,
+ bool* aAny,
+ bool* aAll,
+ nsAString* outValue,
+ bool aCheckDefaults)
+{
+ *aAny = false;
+ *aAll = true;
+ *aFirst = false;
+ bool first = true;
+
+ RefPtr<Selection> selection = GetSelection();
+ NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
+
+ bool isCollapsed = selection->Collapsed();
+ RefPtr<nsRange> range = selection->GetRangeAt(0);
+ // XXX: Should be a while loop, to get each separate range
+ // XXX: ERROR_HANDLING can currentItem be null?
+ if (range) {
+ // For each range, set a flag
+ bool firstNodeInRange = true;
+
+ if (isCollapsed) {
+ nsCOMPtr<nsINode> collapsedNode = range->GetStartParent();
+ NS_ENSURE_TRUE(collapsedNode, NS_ERROR_FAILURE);
+ bool isSet, theSetting;
+ nsString tOutString;
+ if (aAttribute) {
+ nsString tString(*aAttribute);
+ mTypeInState->GetTypingState(isSet, theSetting, &aProperty, tString,
+ &tOutString);
+ if (outValue) {
+ outValue->Assign(tOutString);
+ }
+ } else {
+ mTypeInState->GetTypingState(isSet, theSetting, &aProperty);
+ }
+ if (isSet) {
+ *aFirst = *aAny = *aAll = theSetting;
+ return NS_OK;
+ }
+
+ if (mCSSEditUtils->IsCSSEditableProperty(collapsedNode, &aProperty,
+ aAttribute)) {
+ if (aValue) {
+ tOutString.Assign(*aValue);
+ }
+ *aFirst = *aAny = *aAll =
+ mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(collapsedNode,
+ &aProperty, aAttribute, tOutString, CSSEditUtils::eComputed);
+ if (outValue) {
+ outValue->Assign(tOutString);
+ }
+ return NS_OK;
+ }
+
+ isSet = IsTextPropertySetByContent(collapsedNode, &aProperty,
+ aAttribute, aValue, outValue);
+ *aFirst = *aAny = *aAll = isSet;
+
+ if (!isSet && aCheckDefaults) {
+ // Style not set, but if it is a default then it will appear if content
+ // is inserted, so we should report it as set (analogous to
+ // TypeInState).
+ int32_t index;
+ if (aAttribute && TypeInState::FindPropInList(&aProperty, *aAttribute,
+ outValue, mDefaultStyles,
+ index)) {
+ *aFirst = *aAny = *aAll = true;
+ if (outValue) {
+ outValue->Assign(mDefaultStyles[index]->value);
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ // Non-collapsed selection
+ nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
+
+ nsAutoString firstValue, theValue;
+
+ nsCOMPtr<nsINode> endNode = range->GetEndParent();
+ int32_t endOffset = range->EndOffset();
+
+ for (iter->Init(range); !iter->IsDone(); iter->Next()) {
+ if (!iter->GetCurrentNode()->IsContent()) {
+ continue;
+ }
+ nsCOMPtr<nsIContent> content = iter->GetCurrentNode()->AsContent();
+
+ if (content->IsHTMLElement(nsGkAtoms::body)) {
+ break;
+ }
+
+ // just ignore any non-editable nodes
+ if (content->GetAsText() && (!IsEditable(content) ||
+ IsEmptyTextNode(this, content))) {
+ continue;
+ }
+ if (content->GetAsText()) {
+ if (!isCollapsed && first && firstNodeInRange) {
+ firstNodeInRange = false;
+ if (range->StartOffset() == (int32_t)content->Length()) {
+ continue;
+ }
+ } else if (content == endNode && !endOffset) {
+ continue;
+ }
+ } else if (content->IsElement()) {
+ // handle non-text leaf nodes here
+ continue;
+ }
+
+ bool isSet = false;
+ if (first) {
+ if (mCSSEditUtils->IsCSSEditableProperty(content, &aProperty,
+ aAttribute)) {
+ // The HTML styles defined by aProperty/aAttribute have a CSS
+ // equivalence in this implementation for node; let's check if it
+ // carries those CSS styles
+ if (aValue) {
+ firstValue.Assign(*aValue);
+ }
+ isSet = mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(content,
+ &aProperty, aAttribute, firstValue, CSSEditUtils::eComputed);
+ } else {
+ isSet = IsTextPropertySetByContent(content, &aProperty, aAttribute,
+ aValue, &firstValue);
+ }
+ *aFirst = isSet;
+ first = false;
+ if (outValue) {
+ *outValue = firstValue;
+ }
+ } else {
+ if (mCSSEditUtils->IsCSSEditableProperty(content, &aProperty,
+ aAttribute)) {
+ // The HTML styles defined by aProperty/aAttribute have a CSS
+ // equivalence in this implementation for node; let's check if it
+ // carries those CSS styles
+ if (aValue) {
+ theValue.Assign(*aValue);
+ }
+ isSet = mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(content,
+ &aProperty, aAttribute, theValue, CSSEditUtils::eComputed);
+ } else {
+ isSet = IsTextPropertySetByContent(content, &aProperty, aAttribute,
+ aValue, &theValue);
+ }
+ if (firstValue != theValue) {
+ *aAll = false;
+ }
+ }
+
+ if (isSet) {
+ *aAny = true;
+ } else {
+ *aAll = false;
+ }
+ }
+ }
+ if (!*aAny) {
+ // make sure that if none of the selection is set, we don't report all is
+ // set
+ *aAll = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::GetInlineProperty(nsIAtom* aProperty,
+ const nsAString& aAttribute,
+ const nsAString& aValue,
+ bool* aFirst,
+ bool* aAny,
+ bool* aAll)
+{
+ NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER);
+ const nsAString *att = nullptr;
+ if (!aAttribute.IsEmpty())
+ att = &aAttribute;
+ const nsAString *val = nullptr;
+ if (!aValue.IsEmpty())
+ val = &aValue;
+ return GetInlinePropertyBase(*aProperty, att, val, aFirst, aAny, aAll, nullptr);
+}
+
+NS_IMETHODIMP
+HTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom* aProperty,
+ const nsAString& aAttribute,
+ const nsAString& aValue,
+ bool* aFirst,
+ bool* aAny,
+ bool* aAll,
+ nsAString& outValue)
+{
+ NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER);
+ const nsAString *att = nullptr;
+ if (!aAttribute.IsEmpty())
+ att = &aAttribute;
+ const nsAString *val = nullptr;
+ if (!aValue.IsEmpty())
+ val = &aValue;
+ return GetInlinePropertyBase(*aProperty, att, val, aFirst, aAny, aAll, &outValue);
+}
+
+NS_IMETHODIMP
+HTMLEditor::RemoveAllInlineProperties()
+{
+ AutoEditBatch batchIt(this);
+ AutoRules beginRulesSniffing(this, EditAction::resetTextProperties,
+ nsIEditor::eNext);
+
+ nsresult rv = RemoveInlinePropertyImpl(nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ApplyDefaultProperties();
+}
+
+NS_IMETHODIMP
+HTMLEditor::RemoveInlineProperty(nsIAtom* aProperty,
+ const nsAString& aAttribute)
+{
+ return RemoveInlinePropertyImpl(aProperty, &aAttribute);
+}
+
+nsresult
+HTMLEditor::RemoveInlinePropertyImpl(nsIAtom* aProperty,
+ const nsAString* aAttribute)
+{
+ MOZ_ASSERT_IF(aProperty, aAttribute);
+ NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
+ ForceCompositionEnd();
+
+ RefPtr<Selection> selection = GetSelection();
+ NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
+
+ if (selection->Collapsed()) {
+ // Manipulating text attributes on a collapsed selection only sets state
+ // for the next text insertion
+
+ // For links, aProperty uses "href", use "a" instead
+ if (aProperty == nsGkAtoms::href || aProperty == nsGkAtoms::name) {
+ aProperty = nsGkAtoms::a;
+ }
+
+ if (aProperty) {
+ mTypeInState->ClearProp(aProperty, *aAttribute);
+ } else {
+ mTypeInState->ClearAllProps();
+ }
+ return NS_OK;
+ }
+
+ AutoEditBatch batchIt(this);
+ AutoRules beginRulesSniffing(this, EditAction::removeTextProperty,
+ nsIEditor::eNext);
+ AutoSelectionRestorer selectionRestorer(selection, this);
+ AutoTransactionsConserveSelection dontSpazMySelection(this);
+
+ bool cancel, handled;
+ TextRulesInfo ruleInfo(EditAction::removeTextProperty);
+ // Protect the edit rules object from dying
+ nsCOMPtr<nsIEditRules> rules(mRules);
+ nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!cancel && !handled) {
+ // Loop through the ranges in the selection
+ uint32_t rangeCount = selection->RangeCount();
+ // Since ranges might be modified by SplitStyleAboveRange, we need hold
+ // current ranges
+ AutoTArray<OwningNonNull<nsRange>, 8> arrayOfRanges;
+ for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
+ arrayOfRanges.AppendElement(*selection->GetRangeAt(rangeIdx));
+ }
+ for (auto& range : arrayOfRanges) {
+ if (aProperty == nsGkAtoms::name) {
+ // Promote range if it starts or end in a named anchor and we want to
+ // remove named anchors
+ rv = PromoteRangeIfStartsOrEndsInNamedAnchor(range);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ // Adjust range to include any ancestors whose children are entirely
+ // selected
+ rv = PromoteInlineRange(range);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Remove this style from ancestors of our range endpoints, splitting
+ // them as appropriate
+ rv = SplitStyleAboveRange(range, aProperty, aAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check for easy case: both range endpoints in same text node
+ nsCOMPtr<nsINode> startNode = range->GetStartParent();
+ nsCOMPtr<nsINode> endNode = range->GetEndParent();
+ if (startNode && startNode == endNode && startNode->GetAsText()) {
+ // We're done with this range!
+ if (IsCSSEnabled() &&
+ mCSSEditUtils->IsCSSEditableProperty(startNode, aProperty,
+ aAttribute)) {
+ // The HTML style defined by aProperty/aAttribute has a CSS
+ // equivalence in this implementation for startNode
+ if (mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(startNode,
+ aProperty, aAttribute, EmptyString(),
+ CSSEditUtils::eComputed)) {
+ // startNode's computed style indicates the CSS equivalence to the
+ // HTML style to remove is applied; but we found no element in the
+ // ancestors of startNode carrying specified styles; assume it
+ // comes from a rule and try to insert a span "inverting" the style
+ if (mCSSEditUtils->IsCSSInvertible(*aProperty, aAttribute)) {
+ NS_NAMED_LITERAL_STRING(value, "-moz-editor-invert-value");
+ SetInlinePropertyOnTextNode(*startNode->GetAsText(),
+ range->StartOffset(),
+ range->EndOffset(), *aProperty,
+ aAttribute, value);
+ }
+ }
+ }
+ } else {
+ // Not the easy case. Range not contained in single text node.
+ nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
+
+ nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
+
+ // Iterate range and build up array
+ for (iter->Init(range); !iter->IsDone(); iter->Next()) {
+ nsCOMPtr<nsINode> node = iter->GetCurrentNode();
+ NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+
+ if (IsEditable(node) && node->IsContent()) {
+ arrayOfNodes.AppendElement(*node->AsContent());
+ }
+ }
+
+ // Loop through the list, remove the property on each node
+ for (auto& node : arrayOfNodes) {
+ rv = RemoveStyleInside(node, aProperty, aAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (IsCSSEnabled() &&
+ mCSSEditUtils->IsCSSEditableProperty(node, aProperty,
+ aAttribute) &&
+ mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(node,
+ aProperty, aAttribute, EmptyString(),
+ CSSEditUtils::eComputed) &&
+ // startNode's computed style indicates the CSS equivalence to
+ // the HTML style to remove is applied; but we found no element
+ // in the ancestors of startNode carrying specified styles;
+ // assume it comes from a rule and let's try to insert a span
+ // "inverting" the style
+ mCSSEditUtils->IsCSSInvertible(*aProperty, aAttribute)) {
+ NS_NAMED_LITERAL_STRING(value, "-moz-editor-invert-value");
+ SetInlinePropertyOnNode(node, *aProperty, aAttribute, value);
+ }
+ }
+ }
+ }
+ }
+ if (!cancel) {
+ // Post-process
+ rv = rules->DidDoAction(selection, &ruleInfo, rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::IncreaseFontSize()
+{
+ return RelativeFontChange(FontSize::incr);
+}
+
+NS_IMETHODIMP
+HTMLEditor::DecreaseFontSize()
+{
+ return RelativeFontChange(FontSize::decr);
+}
+
+nsresult
+HTMLEditor::RelativeFontChange(FontSize aDir)
+{
+ ForceCompositionEnd();
+
+ // Get the selection
+ RefPtr<Selection> selection = GetSelection();
+ NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
+ // If selection is collapsed, set typing state
+ if (selection->Collapsed()) {
+ nsIAtom& atom = aDir == FontSize::incr ? *nsGkAtoms::big :
+ *nsGkAtoms::small;
+
+ // Let's see in what kind of element the selection is
+ NS_ENSURE_TRUE(selection->RangeCount() &&
+ selection->GetRangeAt(0)->GetStartParent(), NS_OK);
+ OwningNonNull<nsINode> selectedNode =
+ *selection->GetRangeAt(0)->GetStartParent();
+ if (IsTextNode(selectedNode)) {
+ NS_ENSURE_TRUE(selectedNode->GetParentNode(), NS_OK);
+ selectedNode = *selectedNode->GetParentNode();
+ }
+ if (!CanContainTag(selectedNode, atom)) {
+ return NS_OK;
+ }
+
+ // Manipulating text attributes on a collapsed selection only sets state
+ // for the next text insertion
+ mTypeInState->SetProp(&atom, EmptyString(), EmptyString());
+ return NS_OK;
+ }
+
+ // Wrap with txn batching, rules sniffing, and selection preservation code
+ AutoEditBatch batchIt(this);
+ AutoRules beginRulesSniffing(this, EditAction::setTextProperty,
+ nsIEditor::eNext);
+ AutoSelectionRestorer selectionRestorer(selection, this);
+ AutoTransactionsConserveSelection dontSpazMySelection(this);
+
+ // Loop through the ranges in the selection
+ uint32_t rangeCount = selection->RangeCount();
+ for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
+ RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
+
+ // Adjust range to include any ancestors with entirely selected children
+ nsresult rv = PromoteInlineRange(*range);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check for easy case: both range endpoints in same text node
+ nsCOMPtr<nsINode> startNode = range->GetStartParent();
+ nsCOMPtr<nsINode> endNode = range->GetEndParent();
+ if (startNode == endNode && IsTextNode(startNode)) {
+ rv = RelativeFontChangeOnTextNode(aDir, *startNode->GetAsText(),
+ range->StartOffset(),
+ range->EndOffset());
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Not the easy case. Range not contained in single text node. There
+ // are up to three phases here. There are all the nodes reported by the
+ // subtree iterator to be processed. And there are potentially a
+ // starting textnode and an ending textnode which are only partially
+ // contained by the range.
+
+ // Let's handle the nodes reported by the iterator. These nodes are
+ // entirely contained in the selection range. We build up a list of them
+ // (since doing operations on the document during iteration would perturb
+ // the iterator).
+
+ OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
+
+ // Iterate range and build up array
+ rv = iter->Init(range);
+ if (NS_SUCCEEDED(rv)) {
+ nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
+ for (; !iter->IsDone(); iter->Next()) {
+ NS_ENSURE_TRUE(iter->GetCurrentNode()->IsContent(), NS_ERROR_FAILURE);
+ OwningNonNull<nsIContent> node = *iter->GetCurrentNode()->AsContent();
+
+ if (IsEditable(node)) {
+ arrayOfNodes.AppendElement(node);
+ }
+ }
+
+ // Now that we have the list, do the font size change on each node
+ for (auto& node : arrayOfNodes) {
+ rv = RelativeFontChangeOnNode(aDir == FontSize::incr ? +1 : -1, node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ // Now check the start and end parents of the range to see if they need
+ // to be separately handled (they do if they are text nodes, due to how
+ // the subtree iterator works - it will not have reported them).
+ if (IsTextNode(startNode) && IsEditable(startNode)) {
+ rv = RelativeFontChangeOnTextNode(aDir, *startNode->GetAsText(),
+ range->StartOffset(),
+ startNode->Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (IsTextNode(endNode) && IsEditable(endNode)) {
+ rv = RelativeFontChangeOnTextNode(aDir, *endNode->GetAsText(), 0,
+ range->EndOffset());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditor::RelativeFontChangeOnTextNode(FontSize aDir,
+ Text& aTextNode,
+ int32_t aStartOffset,
+ int32_t aEndOffset)
+{
+ // Don't need to do anything if no characters actually selected
+ if (aStartOffset == aEndOffset) {
+ return NS_OK;
+ }
+
+ if (!aTextNode.GetParentNode() ||
+ !CanContainTag(*aTextNode.GetParentNode(), *nsGkAtoms::big)) {
+ return NS_OK;
+ }
+
+ OwningNonNull<nsIContent> node = aTextNode;
+
+ // Do we need to split the text node?
+
+ // -1 is a magic value meaning to the end of node
+ if (aEndOffset == -1) {
+ aEndOffset = aTextNode.Length();
+ }
+
+ ErrorResult rv;
+ if ((uint32_t)aEndOffset != aTextNode.Length()) {
+ // We need to split off back of text node
+ node = SplitNode(node, aEndOffset, rv);
+ NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
+ }
+ if (aStartOffset) {
+ // We need to split off front of text node
+ SplitNode(node, aStartOffset, rv);
+ NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
+ }
+
+ // Look for siblings that are correct type of node
+ nsIAtom* nodeType = aDir == FontSize::incr ? nsGkAtoms::big
+ : nsGkAtoms::small;
+ nsCOMPtr<nsIContent> sibling = GetPriorHTMLSibling(node);
+ if (sibling && sibling->IsHTMLElement(nodeType)) {
+ // Previous sib is already right kind of inline node; slide this over
+ nsresult rv = MoveNode(node, sibling, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ sibling = GetNextHTMLSibling(node);
+ if (sibling && sibling->IsHTMLElement(nodeType)) {
+ // Following sib is already right kind of inline node; slide this over
+ nsresult rv = MoveNode(node, sibling, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ // Else reparent the node inside font node with appropriate relative size
+ nsCOMPtr<Element> newElement = InsertContainerAbove(node, nodeType);
+ NS_ENSURE_STATE(newElement);
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditor::RelativeFontChangeHelper(int32_t aSizeChange,
+ nsINode* aNode)
+{
+ MOZ_ASSERT(aNode);
+
+ /* This routine looks for all the font nodes in the tree rooted by aNode,
+ including aNode itself, looking for font nodes that have the size attr
+ set. Any such nodes need to have big or small put inside them, since
+ they override any big/small that are above them.
+ */
+
+ // Can only change font size by + or - 1
+ if (aSizeChange != 1 && aSizeChange != -1) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // If this is a font node with size, put big/small inside it.
+ if (aNode->IsHTMLElement(nsGkAtoms::font) &&
+ aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::size)) {
+ // Cycle through children and adjust relative font size.
+ for (uint32_t i = aNode->GetChildCount(); i--; ) {
+ nsresult rv = RelativeFontChangeOnNode(aSizeChange, aNode->GetChildAt(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // RelativeFontChangeOnNode already calls us recursively,
+ // so we don't need to check our children again.
+ return NS_OK;
+ }
+
+ // Otherwise cycle through the children.
+ for (uint32_t i = aNode->GetChildCount(); i--; ) {
+ nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode->GetChildAt(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange,
+ nsIContent* aNode)
+{
+ MOZ_ASSERT(aNode);
+ // Can only change font size by + or - 1
+ if (aSizeChange != 1 && aSizeChange != -1) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsIAtom* atom;
+ if (aSizeChange == 1) {
+ atom = nsGkAtoms::big;
+ } else {
+ atom = nsGkAtoms::small;
+ }
+
+ // Is it the opposite of what we want?
+ if ((aSizeChange == 1 && aNode->IsHTMLElement(nsGkAtoms::small)) ||
+ (aSizeChange == -1 && aNode->IsHTMLElement(nsGkAtoms::big))) {
+ // first populate any nested font tags that have the size attr set
+ nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // in that case, just remove this node and pull up the children
+ return RemoveContainer(aNode);
+ }
+
+ // can it be put inside a "big" or "small"?
+ if (TagCanContain(*atom, *aNode)) {
+ // first populate any nested font tags that have the size attr set
+ nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ok, chuck it in.
+ // first look at siblings of aNode for matching bigs or smalls.
+ // if we find one, move aNode into it.
+ nsIContent* sibling = GetPriorHTMLSibling(aNode);
+ if (sibling && sibling->IsHTMLElement(atom)) {
+ // previous sib is already right kind of inline node; slide this over into it
+ return MoveNode(aNode, sibling, -1);
+ }
+
+ sibling = GetNextHTMLSibling(aNode);
+ if (sibling && sibling->IsHTMLElement(atom)) {
+ // following sib is already right kind of inline node; slide this over into it
+ return MoveNode(aNode, sibling, 0);
+ }
+
+ // else insert it above aNode
+ nsCOMPtr<Element> newElement = InsertContainerAbove(aNode, atom);
+ NS_ENSURE_STATE(newElement);
+
+ return NS_OK;
+ }
+
+ // none of the above? then cycle through the children.
+ // MOOSE: we should group the children together if possible
+ // into a single "big" or "small". For the moment they are
+ // each getting their own.
+ for (uint32_t i = aNode->GetChildCount(); i--; ) {
+ nsresult rv = RelativeFontChangeOnNode(aSizeChange, aNode->GetChildAt(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::GetFontFaceState(bool* aMixed,
+ nsAString& outFace)
+{
+ NS_ENSURE_TRUE(aMixed, NS_ERROR_FAILURE);
+ *aMixed = true;
+ outFace.Truncate();
+
+ bool first, any, all;
+
+ NS_NAMED_LITERAL_STRING(attr, "face");
+ nsresult rv =
+ GetInlinePropertyBase(*nsGkAtoms::font, &attr, nullptr, &first, &any,
+ &all, &outFace);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (any && !all) {
+ return NS_OK; // mixed
+ }
+ if (all) {
+ *aMixed = false;
+ return NS_OK;
+ }
+
+ // if there is no font face, check for tt
+ rv = GetInlinePropertyBase(*nsGkAtoms::tt, nullptr, nullptr, &first, &any,
+ &all,nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (any && !all) {
+ return rv; // mixed
+ }
+ if (all) {
+ *aMixed = false;
+ outFace.AssignLiteral("tt");
+ }
+
+ if (!any) {
+ // there was no font face attrs of any kind. We are in normal font.
+ outFace.Truncate();
+ *aMixed = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::GetFontColorState(bool* aMixed,
+ nsAString& aOutColor)
+{
+ NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
+ *aMixed = true;
+ aOutColor.Truncate();
+
+ NS_NAMED_LITERAL_STRING(colorStr, "color");
+ bool first, any, all;
+
+ nsresult rv =
+ GetInlinePropertyBase(*nsGkAtoms::font, &colorStr, nullptr, &first,
+ &any, &all, &aOutColor);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (any && !all) {
+ return NS_OK; // mixed
+ }
+ if (all) {
+ *aMixed = false;
+ return NS_OK;
+ }
+
+ if (!any) {
+ // there was no font color attrs of any kind..
+ aOutColor.Truncate();
+ *aMixed = false;
+ }
+ return NS_OK;
+}
+
+// the return value is true only if the instance of the HTML editor we created
+// can handle CSS styles (for instance, Composer can, Messenger can't) and if
+// the CSS preference is checked
+nsresult
+HTMLEditor::GetIsCSSEnabled(bool* aIsCSSEnabled)
+{
+ *aIsCSSEnabled = IsCSSEnabled();
+ return NS_OK;
+}
+
+static bool
+HasNonEmptyAttribute(Element* aElement,
+ nsIAtom* aName)
+{
+ MOZ_ASSERT(aElement);
+
+ nsAutoString value;
+ return aElement->GetAttr(kNameSpaceID_None, aName, value) && !value.IsEmpty();
+}
+
+bool
+HTMLEditor::HasStyleOrIdOrClass(Element* aElement)
+{
+ MOZ_ASSERT(aElement);
+
+ // remove the node if its style attribute is empty or absent,
+ // and if it does not have a class nor an id
+ return HasNonEmptyAttribute(aElement, nsGkAtoms::style) ||
+ HasNonEmptyAttribute(aElement, nsGkAtoms::_class) ||
+ HasNonEmptyAttribute(aElement, nsGkAtoms::id);
+}
+
+nsresult
+HTMLEditor::RemoveElementIfNoStyleOrIdOrClass(Element& aElement)
+{
+ // early way out if node is not the right kind of element
+ if ((!aElement.IsHTMLElement(nsGkAtoms::span) &&
+ !aElement.IsHTMLElement(nsGkAtoms::font)) ||
+ HasStyleOrIdOrClass(&aElement)) {
+ return NS_OK;
+ }
+
+ return RemoveContainer(&aElement);
+}
+
+} // namespace mozilla