michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include "TypeInState.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/mozalloc.h" michael@0: #include "nsAString.h" michael@0: #include "nsAttrName.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsCaseTreatment.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsDebug.h" michael@0: #include "nsEditProperty.h" michael@0: #include "nsEditRules.h" michael@0: #include "nsEditor.h" michael@0: #include "nsEditorUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsHTMLCSSUtils.h" michael@0: #include "nsHTMLEditUtils.h" michael@0: #include "nsHTMLEditor.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIContentIterator.h" michael@0: #include "nsIDOMCharacterData.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIEditorIMESupport.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsINode.h" michael@0: #include "nsISelection.h" michael@0: #include "nsISelectionPrivate.h" michael@0: #include "nsISupportsImpl.h" michael@0: #include "nsLiteralString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsSelectionState.h" michael@0: #include "nsString.h" michael@0: #include "nsStringFwd.h" michael@0: #include "nsTArray.h" michael@0: #include "nsTextEditRules.h" michael@0: #include "nsTextEditUtils.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nscore.h" michael@0: michael@0: class nsISupports; michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: static bool michael@0: IsEmptyTextNode(nsHTMLEditor* aThis, nsINode* aNode) michael@0: { michael@0: bool isEmptyTextNode = false; michael@0: return nsEditor::IsTextNode(aNode) && michael@0: NS_SUCCEEDED(aThis->IsEmptyNode(aNode, &isEmptyTextNode)) && michael@0: isEmptyTextNode; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::AddDefaultProperty(nsIAtom *aProperty, michael@0: const nsAString & aAttribute, michael@0: const nsAString & aValue) michael@0: { michael@0: nsString outValue; michael@0: int32_t index; michael@0: nsString attr(aAttribute); michael@0: if (TypeInState::FindPropInList(aProperty, attr, &outValue, mDefaultStyles, index)) michael@0: { michael@0: PropItem *item = mDefaultStyles[index]; michael@0: item->value = aValue; michael@0: } michael@0: else michael@0: { michael@0: nsString value(aValue); michael@0: PropItem *propItem = new PropItem(aProperty, attr, value); michael@0: mDefaultStyles.AppendElement(propItem); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::RemoveDefaultProperty(nsIAtom *aProperty, michael@0: const nsAString & aAttribute, michael@0: const nsAString & aValue) michael@0: { michael@0: nsString outValue; michael@0: int32_t index; michael@0: nsString attr(aAttribute); michael@0: if (TypeInState::FindPropInList(aProperty, attr, &outValue, mDefaultStyles, index)) michael@0: { michael@0: delete mDefaultStyles[index]; michael@0: mDefaultStyles.RemoveElementAt(index); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::RemoveAllDefaultProperties() michael@0: { michael@0: uint32_t j, defcon = mDefaultStyles.Length(); michael@0: for (j=0; j selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (selection->Collapsed()) { michael@0: // manipulating text attributes on a collapsed selection only sets state michael@0: // for the next text insertion michael@0: mTypeInState->SetProp(aProperty, aAttribute, aValue); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoEditBatch batchIt(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::insertElement, nsIEditor::eNext); michael@0: nsAutoSelectionReset selectionResetter(selection, this); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(this); michael@0: michael@0: bool cancel, handled; michael@0: nsTextRulesInfo ruleInfo(EditAction::setTextProperty); michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!cancel && !handled) { michael@0: // loop thru the ranges in the selection michael@0: uint32_t rangeCount = selection->GetRangeCount(); michael@0: for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { michael@0: nsRefPtr range = selection->GetRangeAt(rangeIdx); michael@0: michael@0: // adjust range to include any ancestors whose children are entirely michael@0: // selected michael@0: res = PromoteInlineRange(range); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // check for easy case: both range endpoints in same text node michael@0: nsCOMPtr startNode, endNode; michael@0: res = range->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (startNode == endNode && IsTextNode(startNode)) { michael@0: int32_t startOffset, endOffset; michael@0: range->GetStartOffset(&startOffset); michael@0: range->GetEndOffset(&endOffset); michael@0: nsCOMPtr nodeAsText = do_QueryInterface(startNode); michael@0: res = SetInlinePropertyOnTextNode(nodeAsText, startOffset, endOffset, michael@0: aProperty, &aAttribute, &aValue); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: continue; michael@0: } michael@0: michael@0: // Not the easy case. Range not contained in single text node. There michael@0: // are up to three phases here. There are all the nodes reported by the michael@0: // subtree iterator to be processed. And there are potentially a michael@0: // starting textnode and an ending textnode which are only partially michael@0: // contained by the range. michael@0: michael@0: // Let's handle the nodes reported by the iterator. These nodes are michael@0: // entirely contained in the selection range. We build up a list of them michael@0: // (since doing operations on the document during iteration would perturb michael@0: // the iterator). michael@0: michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &res); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(iter, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMArray arrayOfNodes; michael@0: michael@0: // iterate range and build up array michael@0: res = iter->Init(range); michael@0: // Init returns an error if there are no nodes in range. This can easily michael@0: // happen with the subtree iterator if the selection doesn't contain any michael@0: // *whole* nodes. michael@0: if (NS_SUCCEEDED(res)) { michael@0: nsCOMPtr node; michael@0: for (; !iter->IsDone(); iter->Next()) { michael@0: node = do_QueryInterface(iter->GetCurrentNode()); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: michael@0: if (IsEditable(node)) { michael@0: arrayOfNodes.AppendObject(node); michael@0: } michael@0: } michael@0: } michael@0: // first check the start parent of the range to see if it needs to michael@0: // be separately handled (it does if it's a text node, due to how the michael@0: // subtree iterator works - it will not have reported it). michael@0: if (IsTextNode(startNode) && IsEditable(startNode)) { michael@0: nsCOMPtr nodeAsText = do_QueryInterface(startNode); michael@0: int32_t startOffset; michael@0: uint32_t textLen; michael@0: range->GetStartOffset(&startOffset); michael@0: nodeAsText->GetLength(&textLen); michael@0: res = SetInlinePropertyOnTextNode(nodeAsText, startOffset, textLen, michael@0: aProperty, &aAttribute, &aValue); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // then loop through the list, set the property on each node michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: int32_t j; michael@0: for (j = 0; j < listCount; j++) { michael@0: res = SetInlinePropertyOnNode(arrayOfNodes[j], aProperty, michael@0: &aAttribute, &aValue); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // last check the end parent of the range to see if it needs to michael@0: // be separately handled (it does if it's a text node, due to how the michael@0: // subtree iterator works - it will not have reported it). michael@0: if (IsTextNode(endNode) && IsEditable(endNode)) { michael@0: nsCOMPtr nodeAsText = do_QueryInterface(endNode); michael@0: int32_t endOffset; michael@0: range->GetEndOffset(&endOffset); michael@0: res = SetInlinePropertyOnTextNode(nodeAsText, 0, endOffset, michael@0: aProperty, &aAttribute, &aValue); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: if (!cancel) { michael@0: // post-process michael@0: return mRules->DidDoAction(selection, &ruleInfo, res); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: // Helper function for SetInlinePropertyOn*: is aNode a simple old , , michael@0: // , etc. that we can reuse instead of creating a new one? michael@0: bool michael@0: nsHTMLEditor::IsSimpleModifiableNode(nsIContent* aContent, michael@0: nsIAtom* aProperty, michael@0: const nsAString* aAttribute, michael@0: const nsAString* aValue) michael@0: { michael@0: // aContent can be null, in which case we'll return false in a few lines michael@0: MOZ_ASSERT(aProperty); michael@0: MOZ_ASSERT_IF(aAttribute, aValue); michael@0: michael@0: nsCOMPtr element = do_QueryInterface(aContent); michael@0: if (!element) { michael@0: return false; michael@0: } michael@0: michael@0: // First check for , , etc. michael@0: if (element->IsHTML(aProperty) && !element->GetAttrCount() && michael@0: (!aAttribute || aAttribute->IsEmpty())) { michael@0: return true; michael@0: } michael@0: michael@0: // Special cases for various equivalencies: , , michael@0: if (!element->GetAttrCount() && michael@0: ((aProperty == nsGkAtoms::b && element->IsHTML(nsGkAtoms::strong)) || michael@0: (aProperty == nsGkAtoms::i && element->IsHTML(nsGkAtoms::em)) || michael@0: (aProperty == nsGkAtoms::strike && element->IsHTML(nsGkAtoms::s)))) { michael@0: return true; michael@0: } michael@0: michael@0: // Now look for things like michael@0: if (aAttribute && !aAttribute->IsEmpty()) { michael@0: nsCOMPtr atom = do_GetAtom(*aAttribute); michael@0: MOZ_ASSERT(atom); michael@0: michael@0: nsString attrValue; michael@0: if (element->IsHTML(aProperty) && IsOnlyAttribute(element, *aAttribute) && michael@0: element->GetAttr(kNameSpaceID_None, atom, attrValue) && michael@0: attrValue.Equals(*aValue, nsCaseInsensitiveStringComparator())) { michael@0: // This is not quite correct, because it excludes cases like michael@0: // being the same as . michael@0: // Property-specific handling is needed (bug 760211). michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // No luck so far. Now we check for a with a single style="" michael@0: // attribute that sets only the style we're looking for, if this type of michael@0: // style supports it michael@0: if (!mHTMLCSSUtils->IsCSSEditableProperty(element, aProperty, aAttribute) || michael@0: !element->IsHTML(nsGkAtoms::span) || element->GetAttrCount() != 1 || michael@0: !element->HasAttr(kNameSpaceID_None, nsGkAtoms::style)) { michael@0: return false; michael@0: } michael@0: michael@0: // Some CSS styles are not so simple. For instance, underline is michael@0: // "text-decoration: underline", which decomposes into four different text-* michael@0: // properties. So for now, we just create a span, add the desired style, and michael@0: // see if it matches. michael@0: nsCOMPtr newSpan; michael@0: nsresult res = CreateHTMLContent(NS_LITERAL_STRING("span"), michael@0: getter_AddRefs(newSpan)); michael@0: NS_ASSERTION(NS_SUCCEEDED(res), "CreateHTMLContent failed"); michael@0: NS_ENSURE_SUCCESS(res, false); michael@0: mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(newSpan, aProperty, michael@0: aAttribute, aValue, michael@0: /*suppress transaction*/ true); michael@0: michael@0: return mHTMLCSSUtils->ElementsSameStyle(newSpan, element); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::SetInlinePropertyOnTextNode( nsIDOMCharacterData *aTextNode, michael@0: int32_t aStartOffset, michael@0: int32_t aEndOffset, michael@0: nsIAtom *aProperty, michael@0: const nsAString *aAttribute, michael@0: const nsAString *aValue) michael@0: { michael@0: MOZ_ASSERT(aValue); michael@0: NS_ENSURE_TRUE(aTextNode, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr parent; michael@0: nsresult res = aTextNode->GetParentNode(getter_AddRefs(parent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (!CanContainTag(parent, aProperty)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // don't need to do anything if no characters actually selected michael@0: if (aStartOffset == aEndOffset) return NS_OK; michael@0: michael@0: nsCOMPtr node = aTextNode; michael@0: michael@0: // don't need to do anything if property already set on node michael@0: bool bHasProp; michael@0: if (mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty, aAttribute)) { michael@0: // the HTML styles defined by aProperty/aAttribute has a CSS equivalence michael@0: // in this implementation for node; let's check if it carries those css styles michael@0: nsAutoString value(*aValue); michael@0: mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(node, aProperty, aAttribute, michael@0: bHasProp, value, michael@0: nsHTMLCSSUtils::eComputed); michael@0: } else { michael@0: IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, bHasProp); michael@0: } michael@0: michael@0: if (bHasProp) return NS_OK; michael@0: michael@0: // do we need to split the text node? michael@0: uint32_t textLen; michael@0: aTextNode->GetLength(&textLen); michael@0: michael@0: if (uint32_t(aEndOffset) != textLen) { michael@0: // we need to split off back of text node michael@0: nsCOMPtr tmp; michael@0: res = SplitNode(node, aEndOffset, getter_AddRefs(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: node = tmp; // remember left node michael@0: } michael@0: michael@0: if (aStartOffset) { michael@0: // we need to split off front of text node michael@0: nsCOMPtr tmp; michael@0: res = SplitNode(node, aStartOffset, getter_AddRefs(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: nsCOMPtr content = do_QueryInterface(node); michael@0: NS_ENSURE_STATE(content); michael@0: michael@0: if (aAttribute) { michael@0: // look for siblings that are correct type of node michael@0: nsIContent* sibling = GetPriorHTMLSibling(content); michael@0: if (IsSimpleModifiableNode(sibling, aProperty, aAttribute, aValue)) { michael@0: // previous sib is already right kind of inline node; slide this over into it michael@0: return MoveNode(node, sibling->AsDOMNode(), -1); michael@0: } michael@0: sibling = GetNextHTMLSibling(content); michael@0: if (IsSimpleModifiableNode(sibling, aProperty, aAttribute, aValue)) { michael@0: // following sib is already right kind of inline node; slide this over into it michael@0: return MoveNode(node, sibling->AsDOMNode(), 0); michael@0: } michael@0: } michael@0: michael@0: // reparent the node inside inline node with appropriate {attribute,value} michael@0: return SetInlinePropertyOnNode(node, aProperty, aAttribute, aValue); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent* aNode, michael@0: nsIAtom* aProperty, michael@0: const nsAString* aAttribute, michael@0: const nsAString* aValue) michael@0: { michael@0: MOZ_ASSERT(aNode && aProperty); michael@0: MOZ_ASSERT(aValue); michael@0: michael@0: // If this is an element that can't be contained in a span, we have to michael@0: // recurse to its children. michael@0: if (!TagCanContain(nsGkAtoms::span, aNode->AsDOMNode())) { michael@0: if (aNode->HasChildren()) { michael@0: nsCOMArray arrayOfNodes; michael@0: michael@0: // Populate the list. michael@0: for (nsIContent* child = aNode->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: if (IsEditable(child) && !IsEmptyTextNode(this, child)) { michael@0: arrayOfNodes.AppendObject(child); michael@0: } michael@0: } michael@0: michael@0: // Then loop through the list, set the property on each node. michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: for (int32_t j = 0; j < listCount; ++j) { michael@0: nsresult rv = SetInlinePropertyOnNode(arrayOfNodes[j], aProperty, michael@0: aAttribute, aValue); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // First check if there's an adjacent sibling we can put our node into. michael@0: nsresult res; michael@0: nsCOMPtr previousSibling = GetPriorHTMLSibling(aNode); michael@0: nsCOMPtr nextSibling = GetNextHTMLSibling(aNode); michael@0: if (IsSimpleModifiableNode(previousSibling, aProperty, aAttribute, aValue)) { michael@0: res = MoveNode(aNode, previousSibling, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (IsSimpleModifiableNode(nextSibling, aProperty, aAttribute, aValue)) { michael@0: res = JoinNodes(previousSibling, nextSibling); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: if (IsSimpleModifiableNode(nextSibling, aProperty, aAttribute, aValue)) { michael@0: res = MoveNode(aNode, nextSibling, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // don't need to do anything if property already set on node michael@0: if (mHTMLCSSUtils->IsCSSEditableProperty(aNode, aProperty, aAttribute)) { michael@0: if (mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet( michael@0: aNode, aProperty, aAttribute, *aValue, nsHTMLCSSUtils::eComputed)) { michael@0: return NS_OK; michael@0: } michael@0: } else if (IsTextPropertySetByContent(aNode, aProperty, michael@0: aAttribute, aValue)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool useCSS = (IsCSSEnabled() && michael@0: mHTMLCSSUtils->IsCSSEditableProperty(aNode, aProperty, aAttribute)) || michael@0: // bgcolor is always done using CSS michael@0: aAttribute->EqualsLiteral("bgcolor"); michael@0: michael@0: if (useCSS) { michael@0: nsCOMPtr tmp; michael@0: // We only add style="" to s with no attributes (bug 746515). If we michael@0: // don't have one, we need to make one. michael@0: if (aNode->IsElement() && aNode->AsElement()->IsHTML(nsGkAtoms::span) && michael@0: !aNode->AsElement()->GetAttrCount()) { michael@0: tmp = aNode->AsElement(); michael@0: } else { michael@0: res = InsertContainerAbove(aNode, getter_AddRefs(tmp), michael@0: NS_LITERAL_STRING("span"), michael@0: nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // Add the CSS styles corresponding to the HTML style request michael@0: int32_t count; michael@0: res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(tmp->AsDOMNode(), michael@0: aProperty, aAttribute, michael@0: aValue, &count, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // is it already the right kind of node, but with wrong attribute? michael@0: if (aNode->Tag() == aProperty) { michael@0: // Just set the attribute on it. michael@0: nsCOMPtr elem = do_QueryInterface(aNode); michael@0: return SetAttribute(elem, *aAttribute, *aValue); michael@0: } michael@0: michael@0: // ok, chuck it in its very own container michael@0: nsAutoString tag; michael@0: aProperty->ToString(tag); michael@0: ToLowerCase(tag); michael@0: nsCOMPtr tmp; michael@0: return InsertContainerAbove(aNode->AsDOMNode(), address_of(tmp), tag, michael@0: aAttribute, aValue); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::SetInlinePropertyOnNode(nsIDOMNode *aNode, michael@0: nsIAtom *aProperty, michael@0: const nsAString *aAttribute, michael@0: const nsAString *aValue) michael@0: { michael@0: // Before setting the property, we remove it if it's already set. michael@0: // RemoveStyleInside might remove the node we're looking at or some of its michael@0: // descendants, however, in which case we want to set the property on michael@0: // whatever wound up in its place. We have to save the original siblings and michael@0: // parent to figure this out. michael@0: NS_ENSURE_TRUE(aNode && aProperty, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_STATE(node); michael@0: michael@0: return SetInlinePropertyOnNode(node, aProperty, aAttribute, aValue); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::SetInlinePropertyOnNode(nsIContent* aNode, michael@0: nsIAtom* aProperty, michael@0: const nsAString* aAttribute, michael@0: const nsAString* aValue) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: MOZ_ASSERT(aProperty); michael@0: michael@0: nsCOMPtr previousSibling = aNode->GetPreviousSibling(), michael@0: nextSibling = aNode->GetNextSibling(); michael@0: nsCOMPtr parent = aNode->GetParentNode(); michael@0: NS_ENSURE_STATE(parent); michael@0: michael@0: nsresult res = RemoveStyleInside(aNode->AsDOMNode(), aProperty, aAttribute); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (aNode->GetParentNode()) { michael@0: // The node is still where it was michael@0: return SetInlinePropertyOnNodeImpl(aNode, aProperty, michael@0: aAttribute, aValue); michael@0: } michael@0: michael@0: // It's vanished. Use the old siblings for reference to construct a michael@0: // list. But first, verify that the previous/next siblings are still michael@0: // where we expect them; otherwise we have to give up. michael@0: if ((previousSibling && previousSibling->GetParentNode() != parent) || michael@0: (nextSibling && nextSibling->GetParentNode() != parent)) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: nsCOMArray nodesToSet; michael@0: nsCOMPtr cur = previousSibling michael@0: ? previousSibling->GetNextSibling() : parent->GetFirstChild(); michael@0: while (cur && cur != nextSibling) { michael@0: if (IsEditable(cur)) { michael@0: nodesToSet.AppendObject(cur); michael@0: } michael@0: cur = cur->GetNextSibling(); michael@0: } michael@0: michael@0: int32_t nodesToSetCount = nodesToSet.Count(); michael@0: for (int32_t k = 0; k < nodesToSetCount; k++) { michael@0: res = SetInlinePropertyOnNodeImpl(nodesToSet[k], aProperty, michael@0: aAttribute, aValue); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult nsHTMLEditor::SplitStyleAboveRange(nsIDOMRange *inRange, michael@0: nsIAtom *aProperty, michael@0: const nsAString *aAttribute) michael@0: { michael@0: NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: nsCOMPtr startNode, endNode, origStartNode; michael@0: int32_t startOffset, endOffset; michael@0: michael@0: res = inRange->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetStartOffset(&startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: origStartNode = startNode; michael@0: michael@0: // split any matching style nodes above the start of range michael@0: { michael@0: nsAutoTrackDOMPoint tracker(mRangeUpdater, address_of(endNode), &endOffset); michael@0: res = SplitStyleAbovePoint(address_of(startNode), &startOffset, aProperty, aAttribute); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // second verse, same as the first... michael@0: res = SplitStyleAbovePoint(address_of(endNode), &endOffset, aProperty, aAttribute); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // reset the range michael@0: res = inRange->SetStart(startNode, startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->SetEnd(endNode, endOffset); michael@0: return res; michael@0: } michael@0: michael@0: nsresult nsHTMLEditor::SplitStyleAbovePoint(nsCOMPtr *aNode, michael@0: int32_t *aOffset, michael@0: nsIAtom *aProperty, // null here means we split all properties michael@0: const nsAString *aAttribute, michael@0: nsCOMPtr *outLeftNode, michael@0: nsCOMPtr *outRightNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && *aNode && aOffset, NS_ERROR_NULL_POINTER); michael@0: if (outLeftNode) *outLeftNode = nullptr; michael@0: if (outRightNode) *outRightNode = nullptr; michael@0: // split any matching style nodes above the node/offset michael@0: nsCOMPtr parent, tmp = *aNode; michael@0: int32_t offset; michael@0: michael@0: bool useCSS = IsCSSEnabled(); michael@0: michael@0: bool isSet; michael@0: while (tmp && !IsBlockNode(tmp)) michael@0: { michael@0: isSet = false; michael@0: if (useCSS && mHTMLCSSUtils->IsCSSEditableProperty(tmp, aProperty, aAttribute)) { michael@0: // the HTML style defined by aProperty/aAttribute has a CSS equivalence michael@0: // in this implementation for the node tmp; let's check if it carries those css styles michael@0: nsAutoString firstValue; michael@0: mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(tmp, aProperty, michael@0: aAttribute, isSet, firstValue, nsHTMLCSSUtils::eSpecified); michael@0: } michael@0: if ( (aProperty && NodeIsType(tmp, aProperty)) || // node is the correct inline prop michael@0: (aProperty == nsEditProperty::href && nsHTMLEditUtils::IsLink(tmp)) || michael@0: // node is href - test if really GetParentNode(getter_AddRefs(*aNode)); michael@0: *aOffset = offset; michael@0: } michael@0: tmp->GetParentNode(getter_AddRefs(parent)); michael@0: tmp = parent; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::ClearStyle(nsCOMPtr* aNode, int32_t* aOffset, michael@0: nsIAtom* aProperty, const nsAString* aAttribute) michael@0: { michael@0: nsCOMPtr leftNode, rightNode, tmp; michael@0: nsresult res = SplitStyleAbovePoint(aNode, aOffset, aProperty, aAttribute, michael@0: address_of(leftNode), michael@0: address_of(rightNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (leftNode) { michael@0: bool bIsEmptyNode; michael@0: IsEmptyNode(leftNode, &bIsEmptyNode, false, true); michael@0: if (bIsEmptyNode) { michael@0: // delete leftNode if it became empty michael@0: res = DeleteNode(leftNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: if (rightNode) { michael@0: nsCOMPtr secondSplitParent = GetLeftmostChild(rightNode); michael@0: // don't try to split non-containers (br's, images, hr's, etc) michael@0: if (!secondSplitParent) { michael@0: secondSplitParent = rightNode; michael@0: } michael@0: nsCOMPtr savedBR; michael@0: if (!IsContainer(secondSplitParent)) { michael@0: if (nsTextEditUtils::IsBreak(secondSplitParent)) { michael@0: savedBR = secondSplitParent; michael@0: } michael@0: michael@0: secondSplitParent->GetParentNode(getter_AddRefs(tmp)); michael@0: secondSplitParent = tmp; michael@0: } michael@0: *aOffset = 0; michael@0: res = SplitStyleAbovePoint(address_of(secondSplitParent), michael@0: aOffset, aProperty, aAttribute, michael@0: address_of(leftNode), address_of(rightNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // should be impossible to not get a new leftnode here michael@0: NS_ENSURE_TRUE(leftNode, NS_ERROR_FAILURE); michael@0: nsCOMPtr newSelParent = GetLeftmostChild(leftNode); michael@0: if (!newSelParent) { michael@0: newSelParent = leftNode; michael@0: } michael@0: // If rightNode starts with a br, suck it out of right node and into michael@0: // leftNode. This is so we you don't revert back to the previous style michael@0: // if you happen to click at the end of a line. michael@0: if (savedBR) { michael@0: res = MoveNode(savedBR, newSelParent, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: bool bIsEmptyNode; michael@0: IsEmptyNode(rightNode, &bIsEmptyNode, false, true); michael@0: if (bIsEmptyNode) { michael@0: // delete rightNode if it became empty michael@0: res = DeleteNode(rightNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // remove the style on this new hierarchy michael@0: int32_t newSelOffset = 0; michael@0: { michael@0: // Track the point at the new hierarchy. This is so we can know where michael@0: // to put the selection after we call RemoveStyleInside(). michael@0: // RemoveStyleInside() could remove any and all of those nodes, so I michael@0: // have to use the range tracking system to find the right spot to put michael@0: // selection. michael@0: nsAutoTrackDOMPoint tracker(mRangeUpdater, michael@0: address_of(newSelParent), &newSelOffset); michael@0: res = RemoveStyleInside(leftNode, aProperty, aAttribute); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // reset our node offset values to the resulting new sel point michael@0: *aNode = newSelParent; michael@0: *aOffset = newSelOffset; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsHTMLEditor::NodeIsProperty(nsIDOMNode *aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, false); michael@0: if (!IsContainer(aNode)) return false; michael@0: if (!IsEditable(aNode)) return false; michael@0: if (IsBlockNode(aNode)) return false; michael@0: if (NodeIsType(aNode, nsEditProperty::a)) return false; michael@0: return true; michael@0: } michael@0: michael@0: nsresult nsHTMLEditor::ApplyDefaultProperties() michael@0: { michael@0: nsresult res = NS_OK; michael@0: uint32_t j, defcon = mDefaultStyles.Length(); michael@0: for (j=0; jtag, propItem->attr, propItem->value); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult nsHTMLEditor::RemoveStyleInside(nsIDOMNode *aNode, michael@0: // null here means remove all properties michael@0: nsIAtom *aProperty, michael@0: const nsAString *aAttribute, michael@0: const bool aChildrenOnly) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: if (IsTextNode(aNode)) { michael@0: return NS_OK; michael@0: } michael@0: nsresult res; michael@0: michael@0: // first process the children michael@0: nsCOMPtr child, tmp; michael@0: aNode->GetFirstChild(getter_AddRefs(child)); michael@0: while (child) { michael@0: // cache next sibling since we might remove child michael@0: child->GetNextSibling(getter_AddRefs(tmp)); michael@0: res = RemoveStyleInside(child, aProperty, aAttribute); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: child = tmp; michael@0: } michael@0: michael@0: // then process the node itself michael@0: if (!aChildrenOnly && michael@0: ( michael@0: // node is prop we asked for michael@0: (aProperty && NodeIsType(aNode, aProperty)) || michael@0: // but check for link (IsEmpty()) { michael@0: NS_NAMED_LITERAL_STRING(styleAttr, "style"); michael@0: NS_NAMED_LITERAL_STRING(classAttr, "class"); michael@0: bool hasStyleAttr = HasAttr(aNode, &styleAttr); michael@0: bool hasClassAttr = HasAttr(aNode, &classAttr); michael@0: if (aProperty && (hasStyleAttr || hasClassAttr)) { michael@0: // aNode carries inline styles or a class attribute so we can't michael@0: // just remove the element... We need to create above the element michael@0: // a span that will carry those styles or class, then we can delete michael@0: // the node. michael@0: nsCOMPtr spanNode; michael@0: res = InsertContainerAbove(aNode, address_of(spanNode), michael@0: NS_LITERAL_STRING("span")); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = CloneAttribute(styleAttr, spanNode, aNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = CloneAttribute(classAttr, spanNode, aNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: res = RemoveContainer(aNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else { michael@0: // otherwise we just want to eliminate the attribute michael@0: if (HasAttr(aNode, aAttribute)) { michael@0: // if this matching attribute is the ONLY one on the node, michael@0: // then remove the whole node. Otherwise just nix the attribute. michael@0: if (IsOnlyAttribute(aNode, aAttribute)) { michael@0: res = RemoveContainer(aNode); michael@0: } else { michael@0: nsCOMPtr elem = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(elem, NS_ERROR_NULL_POINTER); michael@0: res = RemoveAttribute(elem, *aAttribute); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!aChildrenOnly && michael@0: mHTMLCSSUtils->IsCSSEditableProperty(aNode, aProperty, aAttribute)) { michael@0: // the HTML style defined by aProperty/aAttribute has a CSS equivalence in michael@0: // this implementation for the node aNode; let's check if it carries those michael@0: // css styles michael@0: nsAutoString propertyValue; michael@0: bool isSet; michael@0: mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(aNode, aProperty, michael@0: aAttribute, isSet, propertyValue, nsHTMLCSSUtils::eSpecified); michael@0: if (isSet) { michael@0: // yes, tmp has the corresponding css declarations in its style attribute michael@0: // let's remove them michael@0: mHTMLCSSUtils->RemoveCSSEquivalentToHTMLStyle(aNode, michael@0: aProperty, michael@0: aAttribute, michael@0: &propertyValue, michael@0: false); michael@0: // remove the node if it is a span or font, if its style attribute is michael@0: // empty or absent, and if it does not have a class nor an id michael@0: RemoveElementIfNoStyleOrIdOrClass(aNode); michael@0: } michael@0: } michael@0: michael@0: if (!aChildrenOnly && michael@0: ( michael@0: (aProperty == nsEditProperty::font) && // or node is big or small and we are setting font size michael@0: (nsHTMLEditUtils::IsBig(aNode) || nsHTMLEditUtils::IsSmall(aNode)) && michael@0: (aAttribute && aAttribute->LowerCaseEqualsLiteral("size")) michael@0: ) michael@0: ) { michael@0: return RemoveContainer(aNode); // if we are setting font size, remove any nested bigs and smalls michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsHTMLEditor::IsOnlyAttribute(nsIDOMNode *aNode, michael@0: const nsAString *aAttribute) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && aAttribute, false); // ooops michael@0: michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(content, false); // ooops michael@0: michael@0: return IsOnlyAttribute(content, *aAttribute); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::IsOnlyAttribute(const nsIContent* aContent, michael@0: const nsAString& aAttribute) michael@0: { michael@0: MOZ_ASSERT(aContent); michael@0: michael@0: uint32_t attrCount = aContent->GetAttrCount(); michael@0: for (uint32_t i = 0; i < attrCount; ++i) { michael@0: const nsAttrName* name = aContent->GetAttrNameAt(i); michael@0: if (!name->NamespaceEquals(kNameSpaceID_None)) { michael@0: return false; michael@0: } michael@0: michael@0: nsAutoString attrString; michael@0: name->LocalName()->ToString(attrString); michael@0: // if it's the attribute we know about, or a special _moz attribute, michael@0: // keep looking michael@0: if (!attrString.Equals(aAttribute, nsCaseInsensitiveStringComparator()) && michael@0: !StringBeginsWith(attrString, NS_LITERAL_STRING("_moz"))) { michael@0: return false; michael@0: } michael@0: } michael@0: // if we made it through all of them without finding a real attribute michael@0: // other than aAttribute, then return true michael@0: return true; michael@0: } michael@0: michael@0: bool nsHTMLEditor::HasAttr(nsIDOMNode* aNode, michael@0: const nsAString* aAttribute) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, false); michael@0: if (!aAttribute || aAttribute->IsEmpty()) { michael@0: // everybody has the 'null' attribute michael@0: return true; michael@0: } michael@0: michael@0: // get element michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(element, false); michael@0: michael@0: nsCOMPtr atom = do_GetAtom(*aAttribute); michael@0: NS_ENSURE_TRUE(atom, false); michael@0: michael@0: return element->HasAttr(kNameSpaceID_None, atom); michael@0: } michael@0: michael@0: michael@0: nsresult nsHTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsIDOMRange *inRange) michael@0: { michael@0: NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: nsCOMPtr startNode, endNode, parent, tmp; michael@0: int32_t startOffset, endOffset, tmpOffset; michael@0: michael@0: res = inRange->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetStartOffset(&startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: tmp = startNode; michael@0: while ( tmp && michael@0: !nsTextEditUtils::IsBody(tmp) && michael@0: !nsHTMLEditUtils::IsNamedAnchor(tmp)) michael@0: { michael@0: parent = GetNodeLocation(tmp, &tmpOffset); michael@0: tmp = parent; michael@0: } michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_NULL_POINTER); michael@0: if (nsHTMLEditUtils::IsNamedAnchor(tmp)) michael@0: { michael@0: parent = GetNodeLocation(tmp, &tmpOffset); michael@0: startNode = parent; michael@0: startOffset = tmpOffset; michael@0: } michael@0: michael@0: tmp = endNode; michael@0: while ( tmp && michael@0: !nsTextEditUtils::IsBody(tmp) && michael@0: !nsHTMLEditUtils::IsNamedAnchor(tmp)) michael@0: { michael@0: parent = GetNodeLocation(tmp, &tmpOffset); michael@0: tmp = parent; michael@0: } michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_NULL_POINTER); michael@0: if (nsHTMLEditUtils::IsNamedAnchor(tmp)) michael@0: { michael@0: parent = GetNodeLocation(tmp, &tmpOffset); michael@0: endNode = parent; michael@0: endOffset = tmpOffset + 1; michael@0: } michael@0: michael@0: res = inRange->SetStart(startNode, startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->SetEnd(endNode, endOffset); michael@0: return res; michael@0: } michael@0: michael@0: nsresult nsHTMLEditor::PromoteInlineRange(nsIDOMRange *inRange) michael@0: { michael@0: NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: nsCOMPtr startNode, endNode, parent; michael@0: int32_t startOffset, endOffset; michael@0: michael@0: res = inRange->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetStartOffset(&startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: while ( startNode && michael@0: !nsTextEditUtils::IsBody(startNode) && michael@0: IsEditable(startNode) && michael@0: IsAtFrontOfNode(startNode, startOffset) ) michael@0: { michael@0: parent = GetNodeLocation(startNode, &startOffset); michael@0: startNode = parent; michael@0: } michael@0: NS_ENSURE_TRUE(startNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: while ( endNode && michael@0: !nsTextEditUtils::IsBody(endNode) && michael@0: IsEditable(endNode) && michael@0: IsAtEndOfNode(endNode, endOffset) ) michael@0: { michael@0: parent = GetNodeLocation(endNode, &endOffset); michael@0: endNode = parent; michael@0: endOffset++; // we are AFTER this node michael@0: } michael@0: NS_ENSURE_TRUE(endNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: res = inRange->SetStart(startNode, startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->SetEnd(endNode, endOffset); michael@0: return res; michael@0: } michael@0: michael@0: bool nsHTMLEditor::IsAtFrontOfNode(nsIDOMNode *aNode, int32_t aOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, false); // oops michael@0: if (!aOffset) { michael@0: return true; michael@0: } michael@0: michael@0: if (IsTextNode(aNode)) michael@0: { michael@0: return false; michael@0: } michael@0: else michael@0: { michael@0: nsCOMPtr firstNode; michael@0: GetFirstEditableChild(aNode, address_of(firstNode)); michael@0: NS_ENSURE_TRUE(firstNode, true); michael@0: int32_t offset = GetChildOffset(firstNode, aNode); michael@0: if (offset < aOffset) return false; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: bool nsHTMLEditor::IsAtEndOfNode(nsIDOMNode *aNode, int32_t aOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, false); // oops michael@0: uint32_t len; michael@0: GetLengthOfDOMNode(aNode, len); michael@0: if (aOffset == (int32_t)len) return true; michael@0: michael@0: if (IsTextNode(aNode)) michael@0: { michael@0: return false; michael@0: } michael@0: else michael@0: { michael@0: nsCOMPtr lastNode; michael@0: GetLastEditableChild(aNode, address_of(lastNode)); michael@0: NS_ENSURE_TRUE(lastNode, true); michael@0: int32_t offset = GetChildOffset(lastNode, aNode); michael@0: if (offset < aOffset) return true; michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetInlinePropertyBase(nsIAtom *aProperty, michael@0: const nsAString *aAttribute, michael@0: const nsAString *aValue, michael@0: bool *aFirst, michael@0: bool *aAny, michael@0: bool *aAll, michael@0: nsAString *outValue, michael@0: bool aCheckDefaults) michael@0: { michael@0: NS_ENSURE_TRUE(aProperty, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult result; michael@0: *aAny = false; michael@0: *aAll = true; michael@0: *aFirst = false; michael@0: bool first = true; michael@0: michael@0: nsCOMPtr selection; michael@0: result = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: Selection* sel = static_cast(selection.get()); michael@0: michael@0: bool isCollapsed = selection->Collapsed(); michael@0: nsCOMPtr collapsedNode; michael@0: nsRefPtr range = sel->GetRangeAt(0); michael@0: // XXX: should be a while loop, to get each separate range michael@0: // XXX: ERROR_HANDLING can currentItem be null? michael@0: if (range) { michael@0: bool firstNodeInRange = true; // for each range, set a flag michael@0: michael@0: if (isCollapsed) { michael@0: range->GetStartContainer(getter_AddRefs(collapsedNode)); michael@0: NS_ENSURE_TRUE(collapsedNode, NS_ERROR_FAILURE); michael@0: bool isSet, theSetting; michael@0: nsString tOutString; michael@0: if (aAttribute) { michael@0: nsString tString(*aAttribute); michael@0: mTypeInState->GetTypingState(isSet, theSetting, aProperty, tString, michael@0: &tOutString); michael@0: if (outValue) { michael@0: outValue->Assign(tOutString); michael@0: } michael@0: } else { michael@0: mTypeInState->GetTypingState(isSet, theSetting, aProperty); michael@0: } michael@0: if (isSet) { michael@0: *aFirst = *aAny = *aAll = theSetting; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mHTMLCSSUtils->IsCSSEditableProperty(collapsedNode, aProperty, aAttribute)) { michael@0: mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet( michael@0: collapsedNode, aProperty, aAttribute, isSet, tOutString, michael@0: nsHTMLCSSUtils::eComputed); michael@0: if (outValue) { michael@0: outValue->Assign(tOutString); michael@0: } michael@0: *aFirst = *aAny = *aAll = isSet; michael@0: return NS_OK; michael@0: } michael@0: michael@0: IsTextPropertySetByContent(collapsedNode, aProperty, aAttribute, aValue, michael@0: isSet, outValue); michael@0: *aFirst = *aAny = *aAll = isSet; michael@0: michael@0: if (!isSet && aCheckDefaults) { michael@0: // style not set, but if it is a default then it will appear if michael@0: // content is inserted, so we should report it as set (analogous to michael@0: // TypeInState). michael@0: int32_t index; michael@0: if (aAttribute && TypeInState::FindPropInList(aProperty, *aAttribute, michael@0: outValue, mDefaultStyles, michael@0: index)) { michael@0: *aFirst = *aAny = *aAll = true; michael@0: if (outValue) { michael@0: outValue->Assign(mDefaultStyles[index]->value); michael@0: } michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // non-collapsed selection michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/post-content-iterator;1"); michael@0: NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsAutoString firstValue, theValue; michael@0: michael@0: nsCOMPtr endNode; michael@0: int32_t endOffset; michael@0: result = range->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: result = range->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: for (iter->Init(range); !iter->IsDone(); iter->Next()) { michael@0: if (!iter->GetCurrentNode()->IsContent()) { michael@0: continue; michael@0: } michael@0: nsCOMPtr content = iter->GetCurrentNode()->AsContent(); michael@0: nsCOMPtr node = content->AsDOMNode(); michael@0: michael@0: if (nsTextEditUtils::IsBody(node)) { michael@0: break; michael@0: } michael@0: michael@0: nsCOMPtr text; michael@0: text = do_QueryInterface(content); michael@0: michael@0: // just ignore any non-editable nodes michael@0: if (text && (!IsEditable(text) || IsEmptyTextNode(this, content))) { michael@0: continue; michael@0: } michael@0: if (text) { michael@0: if (!isCollapsed && first && firstNodeInRange) { michael@0: firstNodeInRange = false; michael@0: int32_t startOffset; michael@0: range->GetStartOffset(&startOffset); michael@0: uint32_t count; michael@0: text->GetLength(&count); michael@0: if (startOffset == (int32_t)count) { michael@0: continue; michael@0: } michael@0: } else if (node == endNode && !endOffset) { michael@0: continue; michael@0: } michael@0: } else if (content->IsElement()) { michael@0: // handle non-text leaf nodes here michael@0: continue; michael@0: } michael@0: michael@0: bool isSet = false; michael@0: if (first) { michael@0: if (mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty, aAttribute)){ michael@0: // the HTML styles defined by aProperty/aAttribute has a CSS michael@0: // equivalence in this implementation for node; let's check if it michael@0: // carries those css styles michael@0: if (aValue) { michael@0: firstValue.Assign(*aValue); michael@0: } michael@0: mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(node, aProperty, michael@0: aAttribute, isSet, firstValue, nsHTMLCSSUtils::eComputed); michael@0: } else { michael@0: IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, isSet, michael@0: &firstValue); michael@0: } michael@0: *aFirst = isSet; michael@0: first = false; michael@0: if (outValue) { michael@0: *outValue = firstValue; michael@0: } michael@0: } else { michael@0: if (mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty, aAttribute)){ michael@0: // the HTML styles defined by aProperty/aAttribute has a CSS equivalence michael@0: // in this implementation for node; let's check if it carries those css styles michael@0: if (aValue) { michael@0: theValue.Assign(*aValue); michael@0: } michael@0: mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(node, aProperty, michael@0: aAttribute, isSet, theValue, nsHTMLCSSUtils::eComputed); michael@0: } else { michael@0: IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, isSet, michael@0: &theValue); michael@0: } michael@0: if (firstValue != theValue) { michael@0: *aAll = false; michael@0: } michael@0: } michael@0: michael@0: if (isSet) { michael@0: *aAny = true; michael@0: } else { michael@0: *aAll = false; michael@0: } michael@0: } michael@0: } michael@0: if (!*aAny) { michael@0: // make sure that if none of the selection is set, we don't report all is michael@0: // set michael@0: *aAll = false; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty, michael@0: const nsAString &aAttribute, michael@0: const nsAString &aValue, michael@0: bool *aFirst, michael@0: bool *aAny, michael@0: bool *aAll) michael@0: { michael@0: NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER); michael@0: const nsAString *att = nullptr; michael@0: if (!aAttribute.IsEmpty()) michael@0: att = &aAttribute; michael@0: const nsAString *val = nullptr; michael@0: if (!aValue.IsEmpty()) michael@0: val = &aValue; michael@0: return GetInlinePropertyBase( aProperty, att, val, aFirst, aAny, aAll, nullptr); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom *aProperty, michael@0: const nsAString &aAttribute, michael@0: const nsAString &aValue, michael@0: bool *aFirst, michael@0: bool *aAny, michael@0: bool *aAll, michael@0: nsAString &outValue) michael@0: { michael@0: NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER); michael@0: const nsAString *att = nullptr; michael@0: if (!aAttribute.IsEmpty()) michael@0: att = &aAttribute; michael@0: const nsAString *val = nullptr; michael@0: if (!aValue.IsEmpty()) michael@0: val = &aValue; michael@0: return GetInlinePropertyBase( aProperty, att, val, aFirst, aAny, aAll, &outValue); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::RemoveAllInlineProperties() michael@0: { michael@0: nsAutoEditBatch batchIt(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::resetTextProperties, nsIEditor::eNext); michael@0: michael@0: nsresult res = RemoveInlinePropertyImpl(nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: return ApplyDefaultProperties(); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::RemoveInlineProperty(nsIAtom *aProperty, const nsAString &aAttribute) michael@0: { michael@0: return RemoveInlinePropertyImpl(aProperty, &aAttribute); michael@0: } michael@0: michael@0: nsresult nsHTMLEditor::RemoveInlinePropertyImpl(nsIAtom *aProperty, const nsAString *aAttribute) michael@0: { michael@0: MOZ_ASSERT_IF(aProperty, aAttribute); michael@0: NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED); michael@0: ForceCompositionEnd(); michael@0: michael@0: nsresult res; michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: bool useCSS = IsCSSEnabled(); michael@0: if (selection->Collapsed()) { michael@0: // manipulating text attributes on a collapsed selection only sets state for the next text insertion michael@0: michael@0: // For links, aProperty uses "href", use "a" instead michael@0: if (aProperty == nsEditProperty::href || michael@0: aProperty == nsEditProperty::name) michael@0: aProperty = nsEditProperty::a; michael@0: michael@0: if (aProperty) { michael@0: mTypeInState->ClearProp(aProperty, *aAttribute); michael@0: } else { michael@0: mTypeInState->ClearAllProps(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoEditBatch batchIt(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::removeTextProperty, nsIEditor::eNext); michael@0: nsAutoSelectionReset selectionResetter(selection, this); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(this); michael@0: michael@0: bool cancel, handled; michael@0: nsTextRulesInfo ruleInfo(EditAction::removeTextProperty); michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!cancel && !handled) michael@0: { michael@0: // loop thru the ranges in the selection michael@0: uint32_t rangeCount = selection->GetRangeCount(); michael@0: for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { michael@0: nsRefPtr range = selection->GetRangeAt(rangeIdx); michael@0: if (aProperty == nsEditProperty::name) michael@0: { michael@0: // promote range if it starts or end in a named anchor and we michael@0: // want to remove named anchors michael@0: res = PromoteRangeIfStartsOrEndsInNamedAnchor(range); michael@0: } michael@0: else { michael@0: // adjust range to include any ancestors who's children are entirely selected michael@0: res = PromoteInlineRange(range); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // remove this style from ancestors of our range endpoints, michael@0: // splitting them as appropriate michael@0: res = SplitStyleAboveRange(range, aProperty, aAttribute); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // check for easy case: both range endpoints in same text node michael@0: nsCOMPtr startNode, endNode; michael@0: res = range->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if ((startNode == endNode) && IsTextNode(startNode)) michael@0: { michael@0: // we're done with this range! michael@0: if (useCSS && mHTMLCSSUtils->IsCSSEditableProperty(startNode, aProperty, aAttribute)) { michael@0: // the HTML style defined by aProperty/aAttribute has a CSS equivalence michael@0: // in this implementation for startNode michael@0: nsAutoString cssValue; michael@0: bool isSet = false; michael@0: mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(startNode, michael@0: aProperty, aAttribute, isSet , cssValue, michael@0: nsHTMLCSSUtils::eComputed); michael@0: if (isSet) { michael@0: // startNode's computed style indicates the CSS equivalence to the HTML style to michael@0: // remove is applied; but we found no element in the ancestors of startNode michael@0: // carrying specified styles; assume it comes from a rule and let's try to michael@0: // insert a span "inverting" the style michael@0: nsAutoString value; value.AssignLiteral("-moz-editor-invert-value"); michael@0: int32_t startOffset, endOffset; michael@0: range->GetStartOffset(&startOffset); michael@0: range->GetEndOffset(&endOffset); michael@0: nsCOMPtr nodeAsText = do_QueryInterface(startNode); michael@0: if (mHTMLCSSUtils->IsCSSInvertable(aProperty, aAttribute)) { michael@0: SetInlinePropertyOnTextNode(nodeAsText, startOffset, endOffset, aProperty, aAttribute, &value); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // not the easy case. range not contained in single text node. michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &res); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(iter, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMArray arrayOfNodes; michael@0: nsCOMPtr node; michael@0: michael@0: // iterate range and build up array michael@0: iter->Init(range); michael@0: while (!iter->IsDone()) michael@0: { michael@0: node = do_QueryInterface(iter->GetCurrentNode()); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: michael@0: if (IsEditable(node)) michael@0: { michael@0: arrayOfNodes.AppendObject(node); michael@0: } michael@0: michael@0: iter->Next(); michael@0: } michael@0: michael@0: // loop through the list, remove the property on each node michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: int32_t j; michael@0: for (j = 0; j < listCount; j++) michael@0: { michael@0: node = arrayOfNodes[j]; michael@0: res = RemoveStyleInside(node, aProperty, aAttribute); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (useCSS && mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty, aAttribute)) { michael@0: // the HTML style defined by aProperty/aAttribute has a CSS equivalence michael@0: // in this implementation for node michael@0: nsAutoString cssValue; michael@0: bool isSet = false; michael@0: mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(node, aProperty, michael@0: aAttribute, isSet , cssValue, nsHTMLCSSUtils::eComputed); michael@0: if (isSet) { michael@0: // startNode's computed style indicates the CSS equivalence to the HTML style to michael@0: // remove is applied; but we found no element in the ancestors of startNode michael@0: // carrying specified styles; assume it comes from a rule and let's try to michael@0: // insert a span "inverting" the style michael@0: if (mHTMLCSSUtils->IsCSSInvertable(aProperty, aAttribute)) { michael@0: nsAutoString value; value.AssignLiteral("-moz-editor-invert-value"); michael@0: SetInlinePropertyOnNode(node, aProperty, aAttribute, &value); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: arrayOfNodes.Clear(); michael@0: } michael@0: } michael@0: } michael@0: if (!cancel) michael@0: { michael@0: // post-process michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::IncreaseFontSize() michael@0: { michael@0: return RelativeFontChange(1); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::DecreaseFontSize() michael@0: { michael@0: return RelativeFontChange(-1); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::RelativeFontChange( int32_t aSizeChange) michael@0: { michael@0: // Can only change font size by + or - 1 michael@0: if ( !( (aSizeChange==1) || (aSizeChange==-1) ) ) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: ForceCompositionEnd(); michael@0: michael@0: // Get the selection michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); michael@0: // Is the selection collapsed? michael@0: // if it's collapsed set typing state michael@0: if (selection->Collapsed()) { michael@0: nsCOMPtr atom; michael@0: if (aSizeChange==1) atom = nsEditProperty::big; michael@0: else atom = nsEditProperty::small; michael@0: michael@0: // Let's see in what kind of element the selection is michael@0: int32_t offset; michael@0: nsCOMPtr selectedNode; michael@0: GetStartNodeAndOffset(selection, getter_AddRefs(selectedNode), &offset); michael@0: NS_ENSURE_TRUE(selectedNode, NS_OK); michael@0: if (IsTextNode(selectedNode)) { michael@0: nsCOMPtr parent; michael@0: nsresult res = selectedNode->GetParentNode(getter_AddRefs(parent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: selectedNode = parent; michael@0: } michael@0: if (!CanContainTag(selectedNode, atom)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // manipulating text attributes on a collapsed selection only sets state for the next text insertion michael@0: mTypeInState->SetProp(atom, EmptyString(), EmptyString()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // wrap with txn batching, rules sniffing, and selection preservation code michael@0: nsAutoEditBatch batchIt(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::setTextProperty, nsIEditor::eNext); michael@0: nsAutoSelectionReset selectionResetter(selection, this); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(this); michael@0: michael@0: // loop thru the ranges in the selection michael@0: uint32_t rangeCount = selection->GetRangeCount(); michael@0: for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { michael@0: nsRefPtr range = selection->GetRangeAt(rangeIdx); michael@0: michael@0: // adjust range to include any ancestors who's children are entirely selected michael@0: nsresult res = PromoteInlineRange(range); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // check for easy case: both range endpoints in same text node michael@0: nsCOMPtr startNode, endNode; michael@0: res = range->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if ((startNode == endNode) && IsTextNode(startNode)) michael@0: { michael@0: int32_t startOffset, endOffset; michael@0: range->GetStartOffset(&startOffset); michael@0: range->GetEndOffset(&endOffset); michael@0: nsCOMPtr nodeAsText = do_QueryInterface(startNode); michael@0: res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, startOffset, endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else michael@0: { michael@0: // not the easy case. range not contained in single text node. michael@0: // there are up to three phases here. There are all the nodes michael@0: // reported by the subtree iterator to be processed. And there michael@0: // are potentially a starting textnode and an ending textnode michael@0: // which are only partially contained by the range. michael@0: michael@0: // lets handle the nodes reported by the iterator. These nodes michael@0: // are entirely contained in the selection range. We build up michael@0: // a list of them (since doing operations on the document during michael@0: // iteration would perturb the iterator). michael@0: michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &res); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(iter, NS_ERROR_FAILURE); michael@0: michael@0: // iterate range and build up array michael@0: res = iter->Init(range); michael@0: if (NS_SUCCEEDED(res)) { michael@0: nsCOMArray arrayOfNodes; michael@0: while (!iter->IsDone()) { michael@0: NS_ENSURE_TRUE(iter->GetCurrentNode()->IsContent(), NS_ERROR_FAILURE); michael@0: nsCOMPtr node = iter->GetCurrentNode()->AsContent(); michael@0: michael@0: if (IsEditable(node)) { michael@0: arrayOfNodes.AppendObject(node); michael@0: } michael@0: michael@0: iter->Next(); michael@0: } michael@0: michael@0: // now that we have the list, do the font size change on each node michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: for (int32_t j = 0; j < listCount; ++j) { michael@0: nsIContent* node = arrayOfNodes[j]; michael@0: res = RelativeFontChangeOnNode(aSizeChange, node); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: arrayOfNodes.Clear(); michael@0: } michael@0: // now check the start and end parents of the range to see if they need to michael@0: // be separately handled (they do if they are text nodes, due to how the michael@0: // subtree iterator works - it will not have reported them). michael@0: if (IsTextNode(startNode) && IsEditable(startNode)) michael@0: { michael@0: nsCOMPtr nodeAsText = do_QueryInterface(startNode); michael@0: int32_t startOffset; michael@0: uint32_t textLen; michael@0: range->GetStartOffset(&startOffset); michael@0: nodeAsText->GetLength(&textLen); michael@0: res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, startOffset, textLen); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: if (IsTextNode(endNode) && IsEditable(endNode)) michael@0: { michael@0: nsCOMPtr nodeAsText = do_QueryInterface(endNode); michael@0: int32_t endOffset; michael@0: range->GetEndOffset(&endOffset); michael@0: res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, 0, endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::RelativeFontChangeOnTextNode( int32_t aSizeChange, michael@0: nsIDOMCharacterData *aTextNode, michael@0: int32_t aStartOffset, michael@0: int32_t aEndOffset) michael@0: { michael@0: // Can only change font size by + or - 1 michael@0: if ( !( (aSizeChange==1) || (aSizeChange==-1) ) ) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: NS_ENSURE_TRUE(aTextNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // don't need to do anything if no characters actually selected michael@0: if (aStartOffset == aEndOffset) return NS_OK; michael@0: michael@0: nsresult res = NS_OK; michael@0: nsCOMPtr parent; michael@0: res = aTextNode->GetParentNode(getter_AddRefs(parent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!CanContainTag(parent, nsGkAtoms::big)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr tmp, node = do_QueryInterface(aTextNode); michael@0: michael@0: // do we need to split the text node? michael@0: uint32_t textLen; michael@0: aTextNode->GetLength(&textLen); michael@0: michael@0: // -1 is a magic value meaning to the end of node michael@0: if (aEndOffset == -1) aEndOffset = textLen; michael@0: michael@0: if ( (uint32_t)aEndOffset != textLen ) michael@0: { michael@0: // we need to split off back of text node michael@0: res = SplitNode(node, aEndOffset, getter_AddRefs(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: node = tmp; // remember left node michael@0: } michael@0: if ( aStartOffset ) michael@0: { michael@0: // we need to split off front of text node michael@0: res = SplitNode(node, aStartOffset, getter_AddRefs(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_STRING(bigSize, "big"); michael@0: NS_NAMED_LITERAL_STRING(smallSize, "small"); michael@0: const nsAString& nodeType = (aSizeChange==1) ? static_cast(bigSize) : static_cast(smallSize); michael@0: // look for siblings that are correct type of node michael@0: nsCOMPtr sibling; michael@0: GetPriorHTMLSibling(node, address_of(sibling)); michael@0: if (sibling && NodeIsType(sibling, (aSizeChange==1) ? nsEditProperty::big : nsEditProperty::small)) michael@0: { michael@0: // previous sib is already right kind of inline node; slide this over into it michael@0: res = MoveNode(node, sibling, -1); michael@0: return res; michael@0: } michael@0: sibling = nullptr; michael@0: GetNextHTMLSibling(node, address_of(sibling)); michael@0: if (sibling && NodeIsType(sibling, (aSizeChange==1) ? nsEditProperty::big : nsEditProperty::small)) michael@0: { michael@0: // following sib is already right kind of inline node; slide this over into it michael@0: res = MoveNode(node, sibling, 0); michael@0: return res; michael@0: } michael@0: michael@0: // else reparent the node inside font node with appropriate relative size michael@0: res = InsertContainerAbove(node, address_of(tmp), nodeType); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::RelativeFontChangeHelper(int32_t aSizeChange, nsINode* aNode) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: michael@0: /* This routine looks for all the font nodes in the tree rooted by aNode, michael@0: including aNode itself, looking for font nodes that have the size attr michael@0: set. Any such nodes need to have big or small put inside them, since michael@0: they override any big/small that are above them. michael@0: */ michael@0: michael@0: // Can only change font size by + or - 1 michael@0: if (aSizeChange != 1 && aSizeChange != -1) { michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: // If this is a font node with size, put big/small inside it. michael@0: if (aNode->IsElement() && aNode->AsElement()->IsHTML(nsGkAtoms::font) && michael@0: aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::size)) { michael@0: // Cycle through children and adjust relative font size. michael@0: for (uint32_t i = aNode->GetChildCount(); i--; ) { michael@0: nsresult rv = RelativeFontChangeOnNode(aSizeChange, aNode->GetChildAt(i)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // RelativeFontChangeOnNode already calls us recursively, michael@0: // so we don't need to check our children again. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Otherwise cycle through the children. michael@0: for (uint32_t i = aNode->GetChildCount(); i--; ) { michael@0: nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode->GetChildAt(i)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange, nsINode* aNode) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: // Can only change font size by + or - 1 michael@0: if (aSizeChange != 1 && aSizeChange != -1) { michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: nsIAtom* atom; michael@0: if (aSizeChange == 1) { michael@0: atom = nsGkAtoms::big; michael@0: } else { michael@0: atom = nsGkAtoms::small; michael@0: } michael@0: michael@0: // Is it the opposite of what we want? michael@0: if (aNode->IsElement() && michael@0: ((aSizeChange == 1 && aNode->AsElement()->IsHTML(nsGkAtoms::small)) || michael@0: (aSizeChange == -1 && aNode->AsElement()->IsHTML(nsGkAtoms::big)))) { michael@0: // first populate any nested font tags that have the size attr set michael@0: nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // in that case, just remove this node and pull up the children michael@0: return RemoveContainer(aNode); michael@0: } michael@0: michael@0: // can it be put inside a "big" or "small"? michael@0: if (TagCanContain(atom, aNode->AsDOMNode())) { michael@0: // first populate any nested font tags that have the size attr set michael@0: nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // ok, chuck it in. michael@0: // first look at siblings of aNode for matching bigs or smalls. michael@0: // if we find one, move aNode into it. michael@0: nsIContent* sibling = GetPriorHTMLSibling(aNode); michael@0: if (sibling && sibling->IsHTML(atom)) { michael@0: // previous sib is already right kind of inline node; slide this over into it michael@0: return MoveNode(aNode->AsDOMNode(), sibling->AsDOMNode(), -1); michael@0: } michael@0: michael@0: sibling = GetNextHTMLSibling(aNode); michael@0: if (sibling && sibling->IsHTML(atom)) { michael@0: // following sib is already right kind of inline node; slide this over into it michael@0: return MoveNode(aNode->AsDOMNode(), sibling->AsDOMNode(), 0); michael@0: } michael@0: michael@0: // else insert it above aNode michael@0: nsCOMPtr tmp; michael@0: return InsertContainerAbove(aNode->AsDOMNode(), address_of(tmp), michael@0: nsAtomString(atom)); michael@0: } michael@0: michael@0: // none of the above? then cycle through the children. michael@0: // MOOSE: we should group the children together if possible michael@0: // into a single "big" or "small". For the moment they are michael@0: // each getting their own. michael@0: for (uint32_t i = aNode->GetChildCount(); i--; ) { michael@0: nsresult rv = RelativeFontChangeOnNode(aSizeChange, aNode->GetChildAt(i)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetFontFaceState(bool *aMixed, nsAString &outFace) michael@0: { michael@0: NS_ENSURE_TRUE(aMixed, NS_ERROR_FAILURE); michael@0: *aMixed = true; michael@0: outFace.Truncate(); michael@0: michael@0: nsresult res; michael@0: bool first, any, all; michael@0: michael@0: NS_NAMED_LITERAL_STRING(attr, "face"); michael@0: res = GetInlinePropertyBase(nsEditProperty::font, &attr, nullptr, &first, &any, &all, &outFace); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (any && !all) return res; // mixed michael@0: if (all) michael@0: { michael@0: *aMixed = false; michael@0: return res; michael@0: } michael@0: michael@0: // if there is no font face, check for tt michael@0: res = GetInlinePropertyBase(nsEditProperty::tt, nullptr, nullptr, &first, &any, &all,nullptr); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (any && !all) return res; // mixed michael@0: if (all) michael@0: { michael@0: *aMixed = false; michael@0: nsEditProperty::tt->ToString(outFace); michael@0: } michael@0: michael@0: if (!any) michael@0: { michael@0: // there was no font face attrs of any kind. We are in normal font. michael@0: outFace.Truncate(); michael@0: *aMixed = false; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetFontColorState(bool *aMixed, nsAString &aOutColor) michael@0: { michael@0: NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER); michael@0: *aMixed = true; michael@0: aOutColor.Truncate(); michael@0: michael@0: nsresult res; michael@0: NS_NAMED_LITERAL_STRING(colorStr, "color"); michael@0: bool first, any, all; michael@0: michael@0: res = GetInlinePropertyBase(nsEditProperty::font, &colorStr, nullptr, &first, &any, &all, &aOutColor); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (any && !all) return res; // mixed michael@0: if (all) michael@0: { michael@0: *aMixed = false; michael@0: return res; michael@0: } michael@0: michael@0: if (!any) michael@0: { michael@0: // there was no font color attrs of any kind.. michael@0: aOutColor.Truncate(); michael@0: *aMixed = false; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: // the return value is true only if the instance of the HTML editor we created michael@0: // can handle CSS styles (for instance, Composer can, Messenger can't) and if michael@0: // the CSS preference is checked michael@0: nsresult michael@0: nsHTMLEditor::GetIsCSSEnabled(bool *aIsCSSEnabled) michael@0: { michael@0: *aIsCSSEnabled = IsCSSEnabled(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: HasNonEmptyAttribute(dom::Element* aElement, nsIAtom* aName) michael@0: { michael@0: MOZ_ASSERT(aElement); michael@0: michael@0: nsAutoString value; michael@0: return aElement->GetAttr(kNameSpaceID_None, aName, value) && !value.IsEmpty(); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::HasStyleOrIdOrClass(dom::Element* aElement) michael@0: { michael@0: MOZ_ASSERT(aElement); michael@0: michael@0: // remove the node if its style attribute is empty or absent, michael@0: // and if it does not have a class nor an id michael@0: return HasNonEmptyAttribute(aElement, nsGkAtoms::style) || michael@0: HasNonEmptyAttribute(aElement, nsGkAtoms::_class) || michael@0: HasNonEmptyAttribute(aElement, nsGkAtoms::id); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::RemoveElementIfNoStyleOrIdOrClass(nsIDOMNode* aElement) michael@0: { michael@0: nsCOMPtr element = do_QueryInterface(aElement); michael@0: NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER); michael@0: michael@0: // early way out if node is not the right kind of element michael@0: if ((!element->IsHTML(nsGkAtoms::span) && michael@0: !element->IsHTML(nsGkAtoms::font)) || michael@0: HasStyleOrIdOrClass(element)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: return RemoveContainer(element); michael@0: }