/* -*- 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 "TextEditRules.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 rules(mRules); ForceCompositionEnd(); RefPtr 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 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 startNode = range->GetStartParent(); nsCOMPtr 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 iter = NS_NewContentSubtreeIterator(); nsTArray> 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 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 , , // , 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 element = do_QueryInterface(aContent); if (!element) { return false; } // First check for , , etc. if (element->IsHTMLElement(aProperty) && !element->GetAttrCount() && (!aAttribute || aAttribute->IsEmpty())) { return true; } // Special cases for various equivalencies: , , 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 if (aAttribute && !aAttribute->IsEmpty()) { nsCOMPtr 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 // 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 (!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 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 = &aText; if (uint32_t(aEndOffset) != aText.Length()) { // We need to split off back of text node nsIContent* textNode = SplitNode(aText, aEndOffset, rv); NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult()); text = textNode->GetAsText(); } 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 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> arrayOfNodes; // Populate the list. for (nsCOMPtr 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 previousSibling = GetPriorHTMLSibling(&aNode); nsCOMPtr 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 attrAtom == nsGkAtoms::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.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 mCSSEditUtils->SetCSSEquivalentToHTMLStyle(tmp, &aProperty, attrAtom, &aValue, false); 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 elem = do_QueryInterface(&aNode); return SetAttribute(elem, *aAttribute, aValue); } // ok, chuck it in its very own container nsCOMPtr 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 previousSibling = aNode.GetPreviousSibling(), nextSibling = aNode.GetNextSibling(); NS_ENSURE_STATE(aNode.GetParentNode()); OwningNonNull 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> nodesToSet; nsCOMPtr 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 startNode = inRange->GetStartParent(); int32_t startOffset = inRange->StartOffset(); nsCOMPtr endNode = inRange->GetEndParent(); int32_t endOffset = inRange->EndOffset(); nsCOMPtr 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* 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 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; isSet = mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet( node, aProperty, aAttribute, firstValue, CSSEditUtils::eSpecified); } if (// node is the correct inline prop (aProperty && node->IsHTMLElement(aProperty)) || // node is href - test if really 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* aNode, int32_t* aOffset, nsIAtom* aProperty, const nsAString* aAttribute) { nsCOMPtr 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 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 (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 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 child = aNode.GetFirstChild(); while (child) { // cache next sibling since we might remove child nsCOMPtr 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 (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 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 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 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 nsCOMPtr attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr; nsAutoString propertyValue; bool isSet = mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(&aNode, aProperty, attribute, 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, attribute, &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 is not nested. nsCOMPtr startNode = aRange.GetStartParent(); int32_t startOffset = aRange.StartOffset(); nsCOMPtr endNode = aRange.GetEndParent(); int32_t endOffset = aRange.EndOffset(); nsCOMPtr 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 startNode = aRange.GetStartParent(); int32_t startOffset = aRange.StartOffset(); nsCOMPtr endNode = aRange.GetEndParent(); int32_t endOffset = aRange.EndOffset(); while (startNode && !startNode->IsHTMLElement(nsGkAtoms::body) && IsEditable(startNode) && IsAtFrontOfNode(*startNode, startOffset)) { nsCOMPtr 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 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 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 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 = GetSelection(); NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); bool isCollapsed = selection->Collapsed(); RefPtr 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 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 iter = NS_NewContentIterator(); nsAutoString firstValue, theValue; nsCOMPtr endNode = range->GetEndParent(); int32_t endOffset = range->EndOffset(); for (iter->Init(range); !iter->IsDone(); iter->Next()) { if (!iter->GetCurrentNode()->IsContent()) { continue; } nsCOMPtr 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 = 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 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, 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 startNode = range->GetStartParent(); nsCOMPtr 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 iter = NS_NewContentSubtreeIterator(); nsTArray> arrayOfNodes; // Iterate range and build up array for (iter->Init(range); !iter->IsDone(); iter->Next()) { nsCOMPtr 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 = 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 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 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 startNode = range->GetStartParent(); nsCOMPtr 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 iter = NS_NewContentSubtreeIterator(); // Iterate range and build up array rv = iter->Init(range); if (NS_SUCCEEDED(rv)) { nsTArray> arrayOfNodes; for (; !iter->IsDone(); iter->Next()) { NS_ENSURE_TRUE(iter->GetCurrentNode()->IsContent(), NS_ERROR_FAILURE); OwningNonNull 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 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 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 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 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