diff -r 000000000000 -r 6474c204b198 editor/libeditor/html/nsHTMLEditorStyle.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/editor/libeditor/html/nsHTMLEditorStyle.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1924 @@ +/* -*- 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 "TypeInState.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/Element.h" +#include "mozilla/mozalloc.h" +#include "nsAString.h" +#include "nsAttrName.h" +#include "nsAutoPtr.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsCaseTreatment.h" +#include "nsComponentManagerUtils.h" +#include "nsDebug.h" +#include "nsEditProperty.h" +#include "nsEditRules.h" +#include "nsEditor.h" +#include "nsEditorUtils.h" +#include "nsError.h" +#include "nsGkAtoms.h" +#include "nsHTMLCSSUtils.h" +#include "nsHTMLEditUtils.h" +#include "nsHTMLEditor.h" +#include "nsIAtom.h" +#include "nsIContent.h" +#include "nsIContentIterator.h" +#include "nsIDOMCharacterData.h" +#include "nsIDOMElement.h" +#include "nsIDOMNode.h" +#include "nsIDOMRange.h" +#include "nsIEditor.h" +#include "nsIEditorIMESupport.h" +#include "nsNameSpaceManager.h" +#include "nsINode.h" +#include "nsISelection.h" +#include "nsISelectionPrivate.h" +#include "nsISupportsImpl.h" +#include "nsLiteralString.h" +#include "nsReadableUtils.h" +#include "nsSelectionState.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nsTextEditRules.h" +#include "nsTextEditUtils.h" +#include "nsUnicharUtils.h" +#include "nscore.h" + +class nsISupports; + +using namespace mozilla; +using namespace mozilla::dom; + +static bool +IsEmptyTextNode(nsHTMLEditor* aThis, nsINode* aNode) +{ + bool isEmptyTextNode = false; + return nsEditor::IsTextNode(aNode) && + NS_SUCCEEDED(aThis->IsEmptyNode(aNode, &isEmptyTextNode)) && + isEmptyTextNode; +} + +NS_IMETHODIMP nsHTMLEditor::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 nsHTMLEditor::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 nsHTMLEditor::RemoveAllDefaultProperties() +{ + uint32_t j, defcon = mDefaultStyles.Length(); + for (j=0; j 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; + } + + nsAutoEditBatch batchIt(this); + nsAutoRules beginRulesSniffing(this, EditAction::insertElement, nsIEditor::eNext); + nsAutoSelectionReset selectionResetter(selection, this); + nsAutoTxnsConserveSelection dontSpazMySelection(this); + + bool cancel, handled; + nsTextRulesInfo ruleInfo(EditAction::setTextProperty); + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); + NS_ENSURE_SUCCESS(res, res); + if (!cancel && !handled) { + // loop thru the ranges in the selection + uint32_t rangeCount = selection->GetRangeCount(); + for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { + nsRefPtr range = selection->GetRangeAt(rangeIdx); + + // adjust range to include any ancestors whose children are entirely + // selected + res = PromoteInlineRange(range); + NS_ENSURE_SUCCESS(res, res); + + // check for easy case: both range endpoints in same text node + nsCOMPtr startNode, endNode; + res = range->GetStartContainer(getter_AddRefs(startNode)); + NS_ENSURE_SUCCESS(res, res); + res = range->GetEndContainer(getter_AddRefs(endNode)); + NS_ENSURE_SUCCESS(res, res); + if (startNode == endNode && IsTextNode(startNode)) { + int32_t startOffset, endOffset; + range->GetStartOffset(&startOffset); + range->GetEndOffset(&endOffset); + nsCOMPtr nodeAsText = do_QueryInterface(startNode); + res = SetInlinePropertyOnTextNode(nodeAsText, startOffset, endOffset, + aProperty, &aAttribute, &aValue); + NS_ENSURE_SUCCESS(res, res); + 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). + + nsCOMPtr iter = + do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &res); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(iter, NS_ERROR_FAILURE); + + nsCOMArray arrayOfNodes; + + // iterate range and build up array + res = 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(res)) { + nsCOMPtr node; + for (; !iter->IsDone(); iter->Next()) { + node = do_QueryInterface(iter->GetCurrentNode()); + NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); + + if (IsEditable(node)) { + arrayOfNodes.AppendObject(node); + } + } + } + // 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 (IsTextNode(startNode) && IsEditable(startNode)) { + nsCOMPtr nodeAsText = do_QueryInterface(startNode); + int32_t startOffset; + uint32_t textLen; + range->GetStartOffset(&startOffset); + nodeAsText->GetLength(&textLen); + res = SetInlinePropertyOnTextNode(nodeAsText, startOffset, textLen, + aProperty, &aAttribute, &aValue); + NS_ENSURE_SUCCESS(res, res); + } + + // then loop through the list, set the property on each node + int32_t listCount = arrayOfNodes.Count(); + int32_t j; + for (j = 0; j < listCount; j++) { + res = SetInlinePropertyOnNode(arrayOfNodes[j], aProperty, + &aAttribute, &aValue); + NS_ENSURE_SUCCESS(res, res); + } + + // 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 (IsTextNode(endNode) && IsEditable(endNode)) { + nsCOMPtr nodeAsText = do_QueryInterface(endNode); + int32_t endOffset; + range->GetEndOffset(&endOffset); + res = SetInlinePropertyOnTextNode(nodeAsText, 0, endOffset, + aProperty, &aAttribute, &aValue); + NS_ENSURE_SUCCESS(res, res); + } + } + } + if (!cancel) { + // post-process + return mRules->DidDoAction(selection, &ruleInfo, res); + } + return NS_OK; +} + + + +// Helper function for SetInlinePropertyOn*: is aNode a simple old , , +// , etc. that we can reuse instead of creating a new one? +bool +nsHTMLEditor::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 element = do_QueryInterface(aContent); + if (!element) { + return false; + } + + // First check for , , etc. + if (element->IsHTML(aProperty) && !element->GetAttrCount() && + (!aAttribute || aAttribute->IsEmpty())) { + return true; + } + + // Special cases for various equivalencies: , , + if (!element->GetAttrCount() && + ((aProperty == nsGkAtoms::b && element->IsHTML(nsGkAtoms::strong)) || + (aProperty == nsGkAtoms::i && element->IsHTML(nsGkAtoms::em)) || + (aProperty == nsGkAtoms::strike && element->IsHTML(nsGkAtoms::s)))) { + return true; + } + + // Now look for things like + if (aAttribute && !aAttribute->IsEmpty()) { + nsCOMPtr atom = do_GetAtom(*aAttribute); + MOZ_ASSERT(atom); + + nsString attrValue; + if (element->IsHTML(aProperty) && IsOnlyAttribute(element, *aAttribute) && + element->GetAttr(kNameSpaceID_None, atom, attrValue) && + attrValue.Equals(*aValue, nsCaseInsensitiveStringComparator())) { + // This is not quite correct, because it excludes cases like + // being the same as . + // Property-specific handling is needed (bug 760211). + return true; + } + } + + // No luck so far. Now we check for a with a single style="" + // attribute that sets only the style we're looking for, if this type of + // style supports it + if (!mHTMLCSSUtils->IsCSSEditableProperty(element, aProperty, aAttribute) || + !element->IsHTML(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 newSpan; + nsresult res = CreateHTMLContent(NS_LITERAL_STRING("span"), + getter_AddRefs(newSpan)); + NS_ASSERTION(NS_SUCCEEDED(res), "CreateHTMLContent failed"); + NS_ENSURE_SUCCESS(res, false); + mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(newSpan, aProperty, + aAttribute, aValue, + /*suppress transaction*/ true); + + return mHTMLCSSUtils->ElementsSameStyle(newSpan, element); +} + + +nsresult +nsHTMLEditor::SetInlinePropertyOnTextNode( nsIDOMCharacterData *aTextNode, + int32_t aStartOffset, + int32_t aEndOffset, + nsIAtom *aProperty, + const nsAString *aAttribute, + const nsAString *aValue) +{ + MOZ_ASSERT(aValue); + NS_ENSURE_TRUE(aTextNode, NS_ERROR_NULL_POINTER); + nsCOMPtr parent; + nsresult res = aTextNode->GetParentNode(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(res, res); + + if (!CanContainTag(parent, aProperty)) { + return NS_OK; + } + + // don't need to do anything if no characters actually selected + if (aStartOffset == aEndOffset) return NS_OK; + + nsCOMPtr node = aTextNode; + + // don't need to do anything if property already set on node + bool bHasProp; + if (mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty, aAttribute)) { + // the HTML styles defined by aProperty/aAttribute has a CSS equivalence + // in this implementation for node; let's check if it carries those css styles + nsAutoString value(*aValue); + mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(node, aProperty, aAttribute, + bHasProp, value, + nsHTMLCSSUtils::eComputed); + } else { + IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, bHasProp); + } + + if (bHasProp) return NS_OK; + + // do we need to split the text node? + uint32_t textLen; + aTextNode->GetLength(&textLen); + + if (uint32_t(aEndOffset) != textLen) { + // we need to split off back of text node + nsCOMPtr tmp; + res = SplitNode(node, aEndOffset, getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(res, res); + node = tmp; // remember left node + } + + if (aStartOffset) { + // we need to split off front of text node + nsCOMPtr tmp; + res = SplitNode(node, aStartOffset, getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(res, res); + } + + nsCOMPtr content = do_QueryInterface(node); + NS_ENSURE_STATE(content); + + if (aAttribute) { + // look for siblings that are correct type of node + nsIContent* sibling = GetPriorHTMLSibling(content); + if (IsSimpleModifiableNode(sibling, aProperty, aAttribute, aValue)) { + // previous sib is already right kind of inline node; slide this over into it + return MoveNode(node, sibling->AsDOMNode(), -1); + } + sibling = GetNextHTMLSibling(content); + if (IsSimpleModifiableNode(sibling, aProperty, aAttribute, aValue)) { + // following sib is already right kind of inline node; slide this over into it + return MoveNode(node, sibling->AsDOMNode(), 0); + } + } + + // reparent the node inside inline node with appropriate {attribute,value} + return SetInlinePropertyOnNode(node, aProperty, aAttribute, aValue); +} + + +nsresult +nsHTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent* aNode, + nsIAtom* aProperty, + const nsAString* aAttribute, + const nsAString* aValue) +{ + MOZ_ASSERT(aNode && aProperty); + MOZ_ASSERT(aValue); + + // 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->AsDOMNode())) { + if (aNode->HasChildren()) { + nsCOMArray arrayOfNodes; + + // Populate the list. + for (nsIContent* child = aNode->GetFirstChild(); + child; + child = child->GetNextSibling()) { + if (IsEditable(child) && !IsEmptyTextNode(this, child)) { + arrayOfNodes.AppendObject(child); + } + } + + // Then loop through the list, set the property on each node. + int32_t listCount = arrayOfNodes.Count(); + for (int32_t j = 0; j < listCount; ++j) { + nsresult rv = SetInlinePropertyOnNode(arrayOfNodes[j], 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. + nsresult res; + nsCOMPtr previousSibling = GetPriorHTMLSibling(aNode); + nsCOMPtr nextSibling = GetNextHTMLSibling(aNode); + if (IsSimpleModifiableNode(previousSibling, aProperty, aAttribute, aValue)) { + res = MoveNode(aNode, previousSibling, -1); + NS_ENSURE_SUCCESS(res, res); + if (IsSimpleModifiableNode(nextSibling, aProperty, aAttribute, aValue)) { + res = JoinNodes(previousSibling, nextSibling); + NS_ENSURE_SUCCESS(res, res); + } + return NS_OK; + } + if (IsSimpleModifiableNode(nextSibling, aProperty, aAttribute, aValue)) { + res = MoveNode(aNode, nextSibling, 0); + NS_ENSURE_SUCCESS(res, res); + return NS_OK; + } + + // don't need to do anything if property already set on node + if (mHTMLCSSUtils->IsCSSEditableProperty(aNode, aProperty, aAttribute)) { + if (mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet( + aNode, aProperty, aAttribute, *aValue, nsHTMLCSSUtils::eComputed)) { + return NS_OK; + } + } else if (IsTextPropertySetByContent(aNode, aProperty, + aAttribute, aValue)) { + return NS_OK; + } + + bool useCSS = (IsCSSEnabled() && + mHTMLCSSUtils->IsCSSEditableProperty(aNode, aProperty, aAttribute)) || + // bgcolor is always done using CSS + aAttribute->EqualsLiteral("bgcolor"); + + if (useCSS) { + nsCOMPtr tmp; + // We only add style="" to s with no attributes (bug 746515). If we + // don't have one, we need to make one. + if (aNode->IsElement() && aNode->AsElement()->IsHTML(nsGkAtoms::span) && + !aNode->AsElement()->GetAttrCount()) { + tmp = aNode->AsElement(); + } else { + res = InsertContainerAbove(aNode, getter_AddRefs(tmp), + NS_LITERAL_STRING("span"), + nullptr, nullptr); + NS_ENSURE_SUCCESS(res, res); + } + + // Add the CSS styles corresponding to the HTML style request + int32_t count; + res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(tmp->AsDOMNode(), + aProperty, aAttribute, + aValue, &count, false); + NS_ENSURE_SUCCESS(res, res); + return NS_OK; + } + + // is it already the right kind of node, but with wrong attribute? + if (aNode->Tag() == aProperty) { + // Just set the attribute on it. + nsCOMPtr elem = do_QueryInterface(aNode); + return SetAttribute(elem, *aAttribute, *aValue); + } + + // ok, chuck it in its very own container + nsAutoString tag; + aProperty->ToString(tag); + ToLowerCase(tag); + nsCOMPtr tmp; + return InsertContainerAbove(aNode->AsDOMNode(), address_of(tmp), tag, + aAttribute, aValue); +} + + +nsresult +nsHTMLEditor::SetInlinePropertyOnNode(nsIDOMNode *aNode, + nsIAtom *aProperty, + const nsAString *aAttribute, + const nsAString *aValue) +{ + // Before setting the property, we remove it if it's already set. + // RemoveStyleInside might remove the node we're looking at or some of its + // descendants, however, in which case we want to set the property on + // whatever wound up in its place. We have to save the original siblings and + // parent to figure this out. + NS_ENSURE_TRUE(aNode && aProperty, NS_ERROR_NULL_POINTER); + + nsCOMPtr node = do_QueryInterface(aNode); + NS_ENSURE_STATE(node); + + return SetInlinePropertyOnNode(node, aProperty, aAttribute, aValue); +} + +nsresult +nsHTMLEditor::SetInlinePropertyOnNode(nsIContent* aNode, + nsIAtom* aProperty, + const nsAString* aAttribute, + const nsAString* aValue) +{ + MOZ_ASSERT(aNode); + MOZ_ASSERT(aProperty); + + nsCOMPtr previousSibling = aNode->GetPreviousSibling(), + nextSibling = aNode->GetNextSibling(); + nsCOMPtr parent = aNode->GetParentNode(); + NS_ENSURE_STATE(parent); + + nsresult res = RemoveStyleInside(aNode->AsDOMNode(), aProperty, aAttribute); + NS_ENSURE_SUCCESS(res, res); + + 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; + } + nsCOMArray nodesToSet; + nsCOMPtr cur = previousSibling + ? previousSibling->GetNextSibling() : parent->GetFirstChild(); + while (cur && cur != nextSibling) { + if (IsEditable(cur)) { + nodesToSet.AppendObject(cur); + } + cur = cur->GetNextSibling(); + } + + int32_t nodesToSetCount = nodesToSet.Count(); + for (int32_t k = 0; k < nodesToSetCount; k++) { + res = SetInlinePropertyOnNodeImpl(nodesToSet[k], aProperty, + aAttribute, aValue); + NS_ENSURE_SUCCESS(res, res); + } + + return NS_OK; +} + + +nsresult nsHTMLEditor::SplitStyleAboveRange(nsIDOMRange *inRange, + nsIAtom *aProperty, + const nsAString *aAttribute) +{ + NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER); + nsresult res; + nsCOMPtr startNode, endNode, origStartNode; + int32_t startOffset, endOffset; + + res = inRange->GetStartContainer(getter_AddRefs(startNode)); + NS_ENSURE_SUCCESS(res, res); + res = inRange->GetStartOffset(&startOffset); + NS_ENSURE_SUCCESS(res, res); + res = inRange->GetEndContainer(getter_AddRefs(endNode)); + NS_ENSURE_SUCCESS(res, res); + res = inRange->GetEndOffset(&endOffset); + NS_ENSURE_SUCCESS(res, res); + + origStartNode = startNode; + + // split any matching style nodes above the start of range + { + nsAutoTrackDOMPoint tracker(mRangeUpdater, address_of(endNode), &endOffset); + res = SplitStyleAbovePoint(address_of(startNode), &startOffset, aProperty, aAttribute); + NS_ENSURE_SUCCESS(res, res); + } + + // second verse, same as the first... + res = SplitStyleAbovePoint(address_of(endNode), &endOffset, aProperty, aAttribute); + NS_ENSURE_SUCCESS(res, res); + + // reset the range + res = inRange->SetStart(startNode, startOffset); + NS_ENSURE_SUCCESS(res, res); + res = inRange->SetEnd(endNode, endOffset); + return res; +} + +nsresult nsHTMLEditor::SplitStyleAbovePoint(nsCOMPtr *aNode, + int32_t *aOffset, + nsIAtom *aProperty, // null here means we split all properties + const nsAString *aAttribute, + nsCOMPtr *outLeftNode, + nsCOMPtr *outRightNode) +{ + NS_ENSURE_TRUE(aNode && *aNode && aOffset, NS_ERROR_NULL_POINTER); + if (outLeftNode) *outLeftNode = nullptr; + if (outRightNode) *outRightNode = nullptr; + // split any matching style nodes above the node/offset + nsCOMPtr parent, tmp = *aNode; + int32_t offset; + + bool useCSS = IsCSSEnabled(); + + bool isSet; + while (tmp && !IsBlockNode(tmp)) + { + isSet = false; + if (useCSS && mHTMLCSSUtils->IsCSSEditableProperty(tmp, aProperty, aAttribute)) { + // the HTML style defined by aProperty/aAttribute has a CSS equivalence + // in this implementation for the node tmp; let's check if it carries those css styles + nsAutoString firstValue; + mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(tmp, aProperty, + aAttribute, isSet, firstValue, nsHTMLCSSUtils::eSpecified); + } + if ( (aProperty && NodeIsType(tmp, aProperty)) || // node is the correct inline prop + (aProperty == nsEditProperty::href && nsHTMLEditUtils::IsLink(tmp)) || + // node is href - test if really GetParentNode(getter_AddRefs(*aNode)); + *aOffset = offset; + } + tmp->GetParentNode(getter_AddRefs(parent)); + tmp = parent; + } + return NS_OK; +} + +nsresult +nsHTMLEditor::ClearStyle(nsCOMPtr* aNode, int32_t* aOffset, + nsIAtom* aProperty, const nsAString* aAttribute) +{ + nsCOMPtr leftNode, rightNode, tmp; + nsresult res = SplitStyleAbovePoint(aNode, aOffset, aProperty, aAttribute, + address_of(leftNode), + address_of(rightNode)); + NS_ENSURE_SUCCESS(res, res); + if (leftNode) { + bool bIsEmptyNode; + IsEmptyNode(leftNode, &bIsEmptyNode, false, true); + if (bIsEmptyNode) { + // delete leftNode if it became empty + res = DeleteNode(leftNode); + NS_ENSURE_SUCCESS(res, res); + } + } + if (rightNode) { + nsCOMPtr secondSplitParent = GetLeftmostChild(rightNode); + // don't try to split non-containers (br's, images, hr's, etc) + if (!secondSplitParent) { + secondSplitParent = rightNode; + } + nsCOMPtr savedBR; + if (!IsContainer(secondSplitParent)) { + if (nsTextEditUtils::IsBreak(secondSplitParent)) { + savedBR = secondSplitParent; + } + + secondSplitParent->GetParentNode(getter_AddRefs(tmp)); + secondSplitParent = tmp; + } + *aOffset = 0; + res = SplitStyleAbovePoint(address_of(secondSplitParent), + aOffset, aProperty, aAttribute, + address_of(leftNode), address_of(rightNode)); + NS_ENSURE_SUCCESS(res, res); + // should be impossible to not get a new leftnode here + NS_ENSURE_TRUE(leftNode, NS_ERROR_FAILURE); + nsCOMPtr 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) { + res = MoveNode(savedBR, newSelParent, 0); + NS_ENSURE_SUCCESS(res, res); + } + bool bIsEmptyNode; + IsEmptyNode(rightNode, &bIsEmptyNode, false, true); + if (bIsEmptyNode) { + // delete rightNode if it became empty + res = DeleteNode(rightNode); + NS_ENSURE_SUCCESS(res, res); + } + // 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. + nsAutoTrackDOMPoint tracker(mRangeUpdater, + address_of(newSelParent), &newSelOffset); + res = RemoveStyleInside(leftNode, aProperty, aAttribute); + NS_ENSURE_SUCCESS(res, res); + } + // reset our node offset values to the resulting new sel point + *aNode = newSelParent; + *aOffset = newSelOffset; + } + + return NS_OK; +} + +bool nsHTMLEditor::NodeIsProperty(nsIDOMNode *aNode) +{ + NS_ENSURE_TRUE(aNode, false); + if (!IsContainer(aNode)) return false; + if (!IsEditable(aNode)) return false; + if (IsBlockNode(aNode)) return false; + if (NodeIsType(aNode, nsEditProperty::a)) return false; + return true; +} + +nsresult nsHTMLEditor::ApplyDefaultProperties() +{ + nsresult res = NS_OK; + uint32_t j, defcon = mDefaultStyles.Length(); + for (j=0; jtag, propItem->attr, propItem->value); + NS_ENSURE_SUCCESS(res, res); + } + return res; +} + +nsresult nsHTMLEditor::RemoveStyleInside(nsIDOMNode *aNode, + // null here means remove all properties + nsIAtom *aProperty, + const nsAString *aAttribute, + const bool aChildrenOnly) +{ + NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); + if (IsTextNode(aNode)) { + return NS_OK; + } + nsresult res; + + // first process the children + nsCOMPtr child, tmp; + aNode->GetFirstChild(getter_AddRefs(child)); + while (child) { + // cache next sibling since we might remove child + child->GetNextSibling(getter_AddRefs(tmp)); + res = RemoveStyleInside(child, aProperty, aAttribute); + NS_ENSURE_SUCCESS(res, res); + child = tmp; + } + + // then process the node itself + if (!aChildrenOnly && + ( + // node is prop we asked for + (aProperty && NodeIsType(aNode, aProperty)) || + // but check for link (IsEmpty()) { + NS_NAMED_LITERAL_STRING(styleAttr, "style"); + NS_NAMED_LITERAL_STRING(classAttr, "class"); + bool hasStyleAttr = HasAttr(aNode, &styleAttr); + bool hasClassAttr = HasAttr(aNode, &classAttr); + 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 spanNode; + res = InsertContainerAbove(aNode, address_of(spanNode), + NS_LITERAL_STRING("span")); + NS_ENSURE_SUCCESS(res, res); + res = CloneAttribute(styleAttr, spanNode, aNode); + NS_ENSURE_SUCCESS(res, res); + res = CloneAttribute(classAttr, spanNode, aNode); + NS_ENSURE_SUCCESS(res, res); + } + res = RemoveContainer(aNode); + NS_ENSURE_SUCCESS(res, res); + } else { + // otherwise we just want to eliminate the attribute + if (HasAttr(aNode, aAttribute)) { + // 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)) { + res = RemoveContainer(aNode); + } else { + nsCOMPtr elem = do_QueryInterface(aNode); + NS_ENSURE_TRUE(elem, NS_ERROR_NULL_POINTER); + res = RemoveAttribute(elem, *aAttribute); + } + NS_ENSURE_SUCCESS(res, res); + } + } + } + + if (!aChildrenOnly && + mHTMLCSSUtils->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; + mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(aNode, aProperty, + aAttribute, isSet, propertyValue, nsHTMLCSSUtils::eSpecified); + if (isSet) { + // yes, tmp has the corresponding css declarations in its style attribute + // let's remove them + mHTMLCSSUtils->RemoveCSSEquivalentToHTMLStyle(aNode, + 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); + } + } + + if (!aChildrenOnly && + ( + (aProperty == nsEditProperty::font) && // or node is big or small and we are setting font size + (nsHTMLEditUtils::IsBig(aNode) || nsHTMLEditUtils::IsSmall(aNode)) && + (aAttribute && aAttribute->LowerCaseEqualsLiteral("size")) + ) + ) { + return RemoveContainer(aNode); // if we are setting font size, remove any nested bigs and smalls + } + return NS_OK; +} + +bool nsHTMLEditor::IsOnlyAttribute(nsIDOMNode *aNode, + const nsAString *aAttribute) +{ + NS_ENSURE_TRUE(aNode && aAttribute, false); // ooops + + nsCOMPtr content = do_QueryInterface(aNode); + NS_ENSURE_TRUE(content, false); // ooops + + return IsOnlyAttribute(content, *aAttribute); +} + +bool +nsHTMLEditor::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; +} + +bool nsHTMLEditor::HasAttr(nsIDOMNode* aNode, + const nsAString* aAttribute) +{ + NS_ENSURE_TRUE(aNode, false); + if (!aAttribute || aAttribute->IsEmpty()) { + // everybody has the 'null' attribute + return true; + } + + // get element + nsCOMPtr element = do_QueryInterface(aNode); + NS_ENSURE_TRUE(element, false); + + nsCOMPtr atom = do_GetAtom(*aAttribute); + NS_ENSURE_TRUE(atom, false); + + return element->HasAttr(kNameSpaceID_None, atom); +} + + +nsresult nsHTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsIDOMRange *inRange) +{ + NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER); + nsresult res; + nsCOMPtr startNode, endNode, parent, tmp; + int32_t startOffset, endOffset, tmpOffset; + + res = inRange->GetStartContainer(getter_AddRefs(startNode)); + NS_ENSURE_SUCCESS(res, res); + res = inRange->GetStartOffset(&startOffset); + NS_ENSURE_SUCCESS(res, res); + res = inRange->GetEndContainer(getter_AddRefs(endNode)); + NS_ENSURE_SUCCESS(res, res); + res = inRange->GetEndOffset(&endOffset); + NS_ENSURE_SUCCESS(res, res); + + tmp = startNode; + while ( tmp && + !nsTextEditUtils::IsBody(tmp) && + !nsHTMLEditUtils::IsNamedAnchor(tmp)) + { + parent = GetNodeLocation(tmp, &tmpOffset); + tmp = parent; + } + NS_ENSURE_TRUE(tmp, NS_ERROR_NULL_POINTER); + if (nsHTMLEditUtils::IsNamedAnchor(tmp)) + { + parent = GetNodeLocation(tmp, &tmpOffset); + startNode = parent; + startOffset = tmpOffset; + } + + tmp = endNode; + while ( tmp && + !nsTextEditUtils::IsBody(tmp) && + !nsHTMLEditUtils::IsNamedAnchor(tmp)) + { + parent = GetNodeLocation(tmp, &tmpOffset); + tmp = parent; + } + NS_ENSURE_TRUE(tmp, NS_ERROR_NULL_POINTER); + if (nsHTMLEditUtils::IsNamedAnchor(tmp)) + { + parent = GetNodeLocation(tmp, &tmpOffset); + endNode = parent; + endOffset = tmpOffset + 1; + } + + res = inRange->SetStart(startNode, startOffset); + NS_ENSURE_SUCCESS(res, res); + res = inRange->SetEnd(endNode, endOffset); + return res; +} + +nsresult nsHTMLEditor::PromoteInlineRange(nsIDOMRange *inRange) +{ + NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER); + nsresult res; + nsCOMPtr startNode, endNode, parent; + int32_t startOffset, endOffset; + + res = inRange->GetStartContainer(getter_AddRefs(startNode)); + NS_ENSURE_SUCCESS(res, res); + res = inRange->GetStartOffset(&startOffset); + NS_ENSURE_SUCCESS(res, res); + res = inRange->GetEndContainer(getter_AddRefs(endNode)); + NS_ENSURE_SUCCESS(res, res); + res = inRange->GetEndOffset(&endOffset); + NS_ENSURE_SUCCESS(res, res); + + while ( startNode && + !nsTextEditUtils::IsBody(startNode) && + IsEditable(startNode) && + IsAtFrontOfNode(startNode, startOffset) ) + { + parent = GetNodeLocation(startNode, &startOffset); + startNode = parent; + } + NS_ENSURE_TRUE(startNode, NS_ERROR_NULL_POINTER); + + while ( endNode && + !nsTextEditUtils::IsBody(endNode) && + IsEditable(endNode) && + IsAtEndOfNode(endNode, endOffset) ) + { + parent = GetNodeLocation(endNode, &endOffset); + endNode = parent; + endOffset++; // we are AFTER this node + } + NS_ENSURE_TRUE(endNode, NS_ERROR_NULL_POINTER); + + res = inRange->SetStart(startNode, startOffset); + NS_ENSURE_SUCCESS(res, res); + res = inRange->SetEnd(endNode, endOffset); + return res; +} + +bool nsHTMLEditor::IsAtFrontOfNode(nsIDOMNode *aNode, int32_t aOffset) +{ + NS_ENSURE_TRUE(aNode, false); // oops + if (!aOffset) { + return true; + } + + if (IsTextNode(aNode)) + { + return false; + } + else + { + nsCOMPtr firstNode; + GetFirstEditableChild(aNode, address_of(firstNode)); + NS_ENSURE_TRUE(firstNode, true); + int32_t offset = GetChildOffset(firstNode, aNode); + if (offset < aOffset) return false; + return true; + } +} + +bool nsHTMLEditor::IsAtEndOfNode(nsIDOMNode *aNode, int32_t aOffset) +{ + NS_ENSURE_TRUE(aNode, false); // oops + uint32_t len; + GetLengthOfDOMNode(aNode, len); + if (aOffset == (int32_t)len) return true; + + if (IsTextNode(aNode)) + { + return false; + } + else + { + nsCOMPtr lastNode; + GetLastEditableChild(aNode, address_of(lastNode)); + NS_ENSURE_TRUE(lastNode, true); + int32_t offset = GetChildOffset(lastNode, aNode); + if (offset < aOffset) return true; + return false; + } +} + + +nsresult +nsHTMLEditor::GetInlinePropertyBase(nsIAtom *aProperty, + const nsAString *aAttribute, + const nsAString *aValue, + bool *aFirst, + bool *aAny, + bool *aAll, + nsAString *outValue, + bool aCheckDefaults) +{ + NS_ENSURE_TRUE(aProperty, NS_ERROR_NULL_POINTER); + + nsresult result; + *aAny = false; + *aAll = true; + *aFirst = false; + bool first = true; + + nsCOMPtr selection; + result = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(result, result); + NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); + Selection* sel = static_cast(selection.get()); + + bool isCollapsed = selection->Collapsed(); + nsCOMPtr collapsedNode; + nsRefPtr range = sel->GetRangeAt(0); + // XXX: should be a while loop, to get each separate range + // XXX: ERROR_HANDLING can currentItem be null? + if (range) { + bool firstNodeInRange = true; // for each range, set a flag + + if (isCollapsed) { + range->GetStartContainer(getter_AddRefs(collapsedNode)); + 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 (mHTMLCSSUtils->IsCSSEditableProperty(collapsedNode, aProperty, aAttribute)) { + mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet( + collapsedNode, aProperty, aAttribute, isSet, tOutString, + nsHTMLCSSUtils::eComputed); + if (outValue) { + outValue->Assign(tOutString); + } + *aFirst = *aAny = *aAll = isSet; + return NS_OK; + } + + IsTextPropertySetByContent(collapsedNode, aProperty, aAttribute, aValue, + isSet, 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 iter = + do_CreateInstance("@mozilla.org/content/post-content-iterator;1"); + NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); + + nsAutoString firstValue, theValue; + + nsCOMPtr endNode; + int32_t endOffset; + result = range->GetEndContainer(getter_AddRefs(endNode)); + NS_ENSURE_SUCCESS(result, result); + result = range->GetEndOffset(&endOffset); + NS_ENSURE_SUCCESS(result, result); + + for (iter->Init(range); !iter->IsDone(); iter->Next()) { + if (!iter->GetCurrentNode()->IsContent()) { + continue; + } + nsCOMPtr content = iter->GetCurrentNode()->AsContent(); + nsCOMPtr node = content->AsDOMNode(); + + if (nsTextEditUtils::IsBody(node)) { + break; + } + + nsCOMPtr text; + text = do_QueryInterface(content); + + // just ignore any non-editable nodes + if (text && (!IsEditable(text) || IsEmptyTextNode(this, content))) { + continue; + } + if (text) { + if (!isCollapsed && first && firstNodeInRange) { + firstNodeInRange = false; + int32_t startOffset; + range->GetStartOffset(&startOffset); + uint32_t count; + text->GetLength(&count); + if (startOffset == (int32_t)count) { + continue; + } + } else if (node == endNode && !endOffset) { + continue; + } + } else if (content->IsElement()) { + // handle non-text leaf nodes here + continue; + } + + bool isSet = false; + if (first) { + if (mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty, aAttribute)){ + // the HTML styles defined by aProperty/aAttribute has a CSS + // equivalence in this implementation for node; let's check if it + // carries those css styles + if (aValue) { + firstValue.Assign(*aValue); + } + mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(node, aProperty, + aAttribute, isSet, firstValue, nsHTMLCSSUtils::eComputed); + } else { + IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, isSet, + &firstValue); + } + *aFirst = isSet; + first = false; + if (outValue) { + *outValue = firstValue; + } + } else { + if (mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty, aAttribute)){ + // the HTML styles defined by aProperty/aAttribute has a CSS equivalence + // in this implementation for node; let's check if it carries those css styles + if (aValue) { + theValue.Assign(*aValue); + } + mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(node, aProperty, + aAttribute, isSet, theValue, nsHTMLCSSUtils::eComputed); + } else { + IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, isSet, + &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 result; +} + + +NS_IMETHODIMP nsHTMLEditor::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 nsHTMLEditor::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 nsHTMLEditor::RemoveAllInlineProperties() +{ + nsAutoEditBatch batchIt(this); + nsAutoRules beginRulesSniffing(this, EditAction::resetTextProperties, nsIEditor::eNext); + + nsresult res = RemoveInlinePropertyImpl(nullptr, nullptr); + NS_ENSURE_SUCCESS(res, res); + return ApplyDefaultProperties(); +} + +NS_IMETHODIMP nsHTMLEditor::RemoveInlineProperty(nsIAtom *aProperty, const nsAString &aAttribute) +{ + return RemoveInlinePropertyImpl(aProperty, &aAttribute); +} + +nsresult nsHTMLEditor::RemoveInlinePropertyImpl(nsIAtom *aProperty, const nsAString *aAttribute) +{ + MOZ_ASSERT_IF(aProperty, aAttribute); + NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED); + ForceCompositionEnd(); + + nsresult res; + nsRefPtr selection = GetSelection(); + NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); + + bool useCSS = IsCSSEnabled(); + 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 == nsEditProperty::href || + aProperty == nsEditProperty::name) + aProperty = nsEditProperty::a; + + if (aProperty) { + mTypeInState->ClearProp(aProperty, *aAttribute); + } else { + mTypeInState->ClearAllProps(); + } + return NS_OK; + } + + nsAutoEditBatch batchIt(this); + nsAutoRules beginRulesSniffing(this, EditAction::removeTextProperty, nsIEditor::eNext); + nsAutoSelectionReset selectionResetter(selection, this); + nsAutoTxnsConserveSelection dontSpazMySelection(this); + + bool cancel, handled; + nsTextRulesInfo ruleInfo(EditAction::removeTextProperty); + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); + NS_ENSURE_SUCCESS(res, res); + if (!cancel && !handled) + { + // loop thru the ranges in the selection + uint32_t rangeCount = selection->GetRangeCount(); + for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { + nsRefPtr range = selection->GetRangeAt(rangeIdx); + if (aProperty == nsEditProperty::name) + { + // promote range if it starts or end in a named anchor and we + // want to remove named anchors + res = PromoteRangeIfStartsOrEndsInNamedAnchor(range); + } + else { + // adjust range to include any ancestors who's children are entirely selected + res = PromoteInlineRange(range); + } + NS_ENSURE_SUCCESS(res, res); + + // remove this style from ancestors of our range endpoints, + // splitting them as appropriate + res = SplitStyleAboveRange(range, aProperty, aAttribute); + NS_ENSURE_SUCCESS(res, res); + + // check for easy case: both range endpoints in same text node + nsCOMPtr startNode, endNode; + res = range->GetStartContainer(getter_AddRefs(startNode)); + NS_ENSURE_SUCCESS(res, res); + res = range->GetEndContainer(getter_AddRefs(endNode)); + NS_ENSURE_SUCCESS(res, res); + if ((startNode == endNode) && IsTextNode(startNode)) + { + // we're done with this range! + if (useCSS && mHTMLCSSUtils->IsCSSEditableProperty(startNode, aProperty, aAttribute)) { + // the HTML style defined by aProperty/aAttribute has a CSS equivalence + // in this implementation for startNode + nsAutoString cssValue; + bool isSet = false; + mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(startNode, + aProperty, aAttribute, isSet , cssValue, + nsHTMLCSSUtils::eComputed); + if (isSet) { + // 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 + nsAutoString value; value.AssignLiteral("-moz-editor-invert-value"); + int32_t startOffset, endOffset; + range->GetStartOffset(&startOffset); + range->GetEndOffset(&endOffset); + nsCOMPtr nodeAsText = do_QueryInterface(startNode); + if (mHTMLCSSUtils->IsCSSInvertable(aProperty, aAttribute)) { + SetInlinePropertyOnTextNode(nodeAsText, startOffset, endOffset, aProperty, aAttribute, &value); + } + } + } + } + else + { + // not the easy case. range not contained in single text node. + nsCOMPtr iter = + do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &res); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(iter, NS_ERROR_FAILURE); + + nsCOMArray arrayOfNodes; + nsCOMPtr node; + + // iterate range and build up array + iter->Init(range); + while (!iter->IsDone()) + { + node = do_QueryInterface(iter->GetCurrentNode()); + NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); + + if (IsEditable(node)) + { + arrayOfNodes.AppendObject(node); + } + + iter->Next(); + } + + // loop through the list, remove the property on each node + int32_t listCount = arrayOfNodes.Count(); + int32_t j; + for (j = 0; j < listCount; j++) + { + node = arrayOfNodes[j]; + res = RemoveStyleInside(node, aProperty, aAttribute); + NS_ENSURE_SUCCESS(res, res); + if (useCSS && mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty, aAttribute)) { + // the HTML style defined by aProperty/aAttribute has a CSS equivalence + // in this implementation for node + nsAutoString cssValue; + bool isSet = false; + mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(node, aProperty, + aAttribute, isSet , cssValue, nsHTMLCSSUtils::eComputed); + if (isSet) { + // 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 + if (mHTMLCSSUtils->IsCSSInvertable(aProperty, aAttribute)) { + nsAutoString value; value.AssignLiteral("-moz-editor-invert-value"); + SetInlinePropertyOnNode(node, aProperty, aAttribute, &value); + } + } + } + } + arrayOfNodes.Clear(); + } + } + } + if (!cancel) + { + // post-process + res = mRules->DidDoAction(selection, &ruleInfo, res); + } + return res; +} + +NS_IMETHODIMP nsHTMLEditor::IncreaseFontSize() +{ + return RelativeFontChange(1); +} + +NS_IMETHODIMP nsHTMLEditor::DecreaseFontSize() +{ + return RelativeFontChange(-1); +} + +nsresult +nsHTMLEditor::RelativeFontChange( int32_t aSizeChange) +{ + // Can only change font size by + or - 1 + if ( !( (aSizeChange==1) || (aSizeChange==-1) ) ) + return NS_ERROR_ILLEGAL_VALUE; + + ForceCompositionEnd(); + + // Get the selection + nsRefPtr selection = GetSelection(); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + // Is the selection collapsed? + // if it's collapsed set typing state + if (selection->Collapsed()) { + nsCOMPtr atom; + if (aSizeChange==1) atom = nsEditProperty::big; + else atom = nsEditProperty::small; + + // Let's see in what kind of element the selection is + int32_t offset; + nsCOMPtr selectedNode; + GetStartNodeAndOffset(selection, getter_AddRefs(selectedNode), &offset); + NS_ENSURE_TRUE(selectedNode, NS_OK); + if (IsTextNode(selectedNode)) { + nsCOMPtr parent; + nsresult res = selectedNode->GetParentNode(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(res, res); + selectedNode = parent; + } + 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 + nsAutoEditBatch batchIt(this); + nsAutoRules beginRulesSniffing(this, EditAction::setTextProperty, nsIEditor::eNext); + nsAutoSelectionReset selectionResetter(selection, this); + nsAutoTxnsConserveSelection dontSpazMySelection(this); + + // loop thru the ranges in the selection + uint32_t rangeCount = selection->GetRangeCount(); + for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { + nsRefPtr range = selection->GetRangeAt(rangeIdx); + + // adjust range to include any ancestors who's children are entirely selected + nsresult res = PromoteInlineRange(range); + NS_ENSURE_SUCCESS(res, res); + + // check for easy case: both range endpoints in same text node + nsCOMPtr startNode, endNode; + res = range->GetStartContainer(getter_AddRefs(startNode)); + NS_ENSURE_SUCCESS(res, res); + res = range->GetEndContainer(getter_AddRefs(endNode)); + NS_ENSURE_SUCCESS(res, res); + if ((startNode == endNode) && IsTextNode(startNode)) + { + int32_t startOffset, endOffset; + range->GetStartOffset(&startOffset); + range->GetEndOffset(&endOffset); + nsCOMPtr nodeAsText = do_QueryInterface(startNode); + res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, startOffset, endOffset); + NS_ENSURE_SUCCESS(res, res); + } + 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. + + // lets 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). + + nsCOMPtr iter = + do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &res); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(iter, NS_ERROR_FAILURE); + + // iterate range and build up array + res = iter->Init(range); + if (NS_SUCCEEDED(res)) { + nsCOMArray arrayOfNodes; + while (!iter->IsDone()) { + NS_ENSURE_TRUE(iter->GetCurrentNode()->IsContent(), NS_ERROR_FAILURE); + nsCOMPtr node = iter->GetCurrentNode()->AsContent(); + + if (IsEditable(node)) { + arrayOfNodes.AppendObject(node); + } + + iter->Next(); + } + + // now that we have the list, do the font size change on each node + int32_t listCount = arrayOfNodes.Count(); + for (int32_t j = 0; j < listCount; ++j) { + nsIContent* node = arrayOfNodes[j]; + res = RelativeFontChangeOnNode(aSizeChange, node); + NS_ENSURE_SUCCESS(res, res); + } + arrayOfNodes.Clear(); + } + // 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)) + { + nsCOMPtr nodeAsText = do_QueryInterface(startNode); + int32_t startOffset; + uint32_t textLen; + range->GetStartOffset(&startOffset); + nodeAsText->GetLength(&textLen); + res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, startOffset, textLen); + NS_ENSURE_SUCCESS(res, res); + } + if (IsTextNode(endNode) && IsEditable(endNode)) + { + nsCOMPtr nodeAsText = do_QueryInterface(endNode); + int32_t endOffset; + range->GetEndOffset(&endOffset); + res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, 0, endOffset); + NS_ENSURE_SUCCESS(res, res); + } + } + } + + return NS_OK; +} + +nsresult +nsHTMLEditor::RelativeFontChangeOnTextNode( int32_t aSizeChange, + nsIDOMCharacterData *aTextNode, + int32_t aStartOffset, + int32_t aEndOffset) +{ + // Can only change font size by + or - 1 + if ( !( (aSizeChange==1) || (aSizeChange==-1) ) ) + return NS_ERROR_ILLEGAL_VALUE; + NS_ENSURE_TRUE(aTextNode, NS_ERROR_NULL_POINTER); + + // don't need to do anything if no characters actually selected + if (aStartOffset == aEndOffset) return NS_OK; + + nsresult res = NS_OK; + nsCOMPtr parent; + res = aTextNode->GetParentNode(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(res, res); + if (!CanContainTag(parent, nsGkAtoms::big)) { + return NS_OK; + } + + nsCOMPtr tmp, node = do_QueryInterface(aTextNode); + + // do we need to split the text node? + uint32_t textLen; + aTextNode->GetLength(&textLen); + + // -1 is a magic value meaning to the end of node + if (aEndOffset == -1) aEndOffset = textLen; + + if ( (uint32_t)aEndOffset != textLen ) + { + // we need to split off back of text node + res = SplitNode(node, aEndOffset, getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(res, res); + node = tmp; // remember left node + } + if ( aStartOffset ) + { + // we need to split off front of text node + res = SplitNode(node, aStartOffset, getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(res, res); + } + + NS_NAMED_LITERAL_STRING(bigSize, "big"); + NS_NAMED_LITERAL_STRING(smallSize, "small"); + const nsAString& nodeType = (aSizeChange==1) ? static_cast(bigSize) : static_cast(smallSize); + // look for siblings that are correct type of node + nsCOMPtr sibling; + GetPriorHTMLSibling(node, address_of(sibling)); + if (sibling && NodeIsType(sibling, (aSizeChange==1) ? nsEditProperty::big : nsEditProperty::small)) + { + // previous sib is already right kind of inline node; slide this over into it + res = MoveNode(node, sibling, -1); + return res; + } + sibling = nullptr; + GetNextHTMLSibling(node, address_of(sibling)); + if (sibling && NodeIsType(sibling, (aSizeChange==1) ? nsEditProperty::big : nsEditProperty::small)) + { + // following sib is already right kind of inline node; slide this over into it + res = MoveNode(node, sibling, 0); + return res; + } + + // else reparent the node inside font node with appropriate relative size + res = InsertContainerAbove(node, address_of(tmp), nodeType); + return res; +} + + +nsresult +nsHTMLEditor::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->IsElement() && aNode->AsElement()->IsHTML(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 +nsHTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange, nsINode* 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 (aNode->IsElement() && + ((aSizeChange == 1 && aNode->AsElement()->IsHTML(nsGkAtoms::small)) || + (aSizeChange == -1 && aNode->AsElement()->IsHTML(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->AsDOMNode())) { + // 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->IsHTML(atom)) { + // previous sib is already right kind of inline node; slide this over into it + return MoveNode(aNode->AsDOMNode(), sibling->AsDOMNode(), -1); + } + + sibling = GetNextHTMLSibling(aNode); + if (sibling && sibling->IsHTML(atom)) { + // following sib is already right kind of inline node; slide this over into it + return MoveNode(aNode->AsDOMNode(), sibling->AsDOMNode(), 0); + } + + // else insert it above aNode + nsCOMPtr tmp; + return InsertContainerAbove(aNode->AsDOMNode(), address_of(tmp), + nsAtomString(atom)); + } + + // 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 +nsHTMLEditor::GetFontFaceState(bool *aMixed, nsAString &outFace) +{ + NS_ENSURE_TRUE(aMixed, NS_ERROR_FAILURE); + *aMixed = true; + outFace.Truncate(); + + nsresult res; + bool first, any, all; + + NS_NAMED_LITERAL_STRING(attr, "face"); + res = GetInlinePropertyBase(nsEditProperty::font, &attr, nullptr, &first, &any, &all, &outFace); + NS_ENSURE_SUCCESS(res, res); + if (any && !all) return res; // mixed + if (all) + { + *aMixed = false; + return res; + } + + // if there is no font face, check for tt + res = GetInlinePropertyBase(nsEditProperty::tt, nullptr, nullptr, &first, &any, &all,nullptr); + NS_ENSURE_SUCCESS(res, res); + if (any && !all) return res; // mixed + if (all) + { + *aMixed = false; + nsEditProperty::tt->ToString(outFace); + } + + if (!any) + { + // there was no font face attrs of any kind. We are in normal font. + outFace.Truncate(); + *aMixed = false; + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::GetFontColorState(bool *aMixed, nsAString &aOutColor) +{ + NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER); + *aMixed = true; + aOutColor.Truncate(); + + nsresult res; + NS_NAMED_LITERAL_STRING(colorStr, "color"); + bool first, any, all; + + res = GetInlinePropertyBase(nsEditProperty::font, &colorStr, nullptr, &first, &any, &all, &aOutColor); + NS_ENSURE_SUCCESS(res, res); + if (any && !all) return res; // mixed + if (all) + { + *aMixed = false; + return res; + } + + if (!any) + { + // there was no font color attrs of any kind.. + aOutColor.Truncate(); + *aMixed = false; + } + return res; +} + +// 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 +nsHTMLEditor::GetIsCSSEnabled(bool *aIsCSSEnabled) +{ + *aIsCSSEnabled = IsCSSEnabled(); + return NS_OK; +} + +static bool +HasNonEmptyAttribute(dom::Element* aElement, nsIAtom* aName) +{ + MOZ_ASSERT(aElement); + + nsAutoString value; + return aElement->GetAttr(kNameSpaceID_None, aName, value) && !value.IsEmpty(); +} + +bool +nsHTMLEditor::HasStyleOrIdOrClass(dom::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 +nsHTMLEditor::RemoveElementIfNoStyleOrIdOrClass(nsIDOMNode* aElement) +{ + nsCOMPtr element = do_QueryInterface(aElement); + NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER); + + // early way out if node is not the right kind of element + if ((!element->IsHTML(nsGkAtoms::span) && + !element->IsHTML(nsGkAtoms::font)) || + HasStyleOrIdOrClass(element)) { + return NS_OK; + } + + return RemoveContainer(element); +}