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: michael@0: /* michael@0: * Implementation of the DOM nsIDOMRange object. michael@0: */ michael@0: michael@0: #include "nscore.h" michael@0: #include "nsRange.h" michael@0: michael@0: #include "nsString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMDocumentFragment.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMText.h" michael@0: #include "nsError.h" michael@0: #include "nsIContentIterator.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsGenericDOMDataNode.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsTextFrame.h" michael@0: #include "nsFontFaceList.h" michael@0: #include "mozilla/dom/DocumentFragment.h" michael@0: #include "mozilla/dom/DocumentType.h" michael@0: #include "mozilla/dom/RangeBinding.h" michael@0: #include "mozilla/dom/DOMRect.h" michael@0: #include "mozilla/dom/ShadowRoot.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: JSObject* michael@0: nsRange::WrapObject(JSContext* aCx) michael@0: { michael@0: return RangeBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: /****************************************************** michael@0: * stack based utilty class for managing monitor michael@0: ******************************************************/ michael@0: michael@0: static void InvalidateAllFrames(nsINode* aNode) michael@0: { michael@0: NS_PRECONDITION(aNode, "bad arg"); michael@0: michael@0: nsIFrame* frame = nullptr; michael@0: switch (aNode->NodeType()) { michael@0: case nsIDOMNode::TEXT_NODE: michael@0: case nsIDOMNode::ELEMENT_NODE: michael@0: { michael@0: nsIContent* content = static_cast(aNode); michael@0: frame = content->GetPrimaryFrame(); michael@0: break; michael@0: } michael@0: case nsIDOMNode::DOCUMENT_NODE: michael@0: { michael@0: nsIDocument* doc = static_cast(aNode); michael@0: nsIPresShell* shell = doc ? doc->GetShell() : nullptr; michael@0: frame = shell ? shell->GetRootFrame() : nullptr; michael@0: break; michael@0: } michael@0: } michael@0: for (nsIFrame* f = frame; f; f = f->GetNextContinuation()) { michael@0: f->InvalidateFrameSubtree(); michael@0: } michael@0: } michael@0: michael@0: // Utility routine to detect if a content node is completely contained in a range michael@0: // If outNodeBefore is returned true, then the node starts before the range does. michael@0: // If outNodeAfter is returned true, then the node ends after the range does. michael@0: // Note that both of the above might be true. michael@0: // If neither are true, the node is contained inside of the range. michael@0: // XXX - callers responsibility to ensure node in same doc as range! michael@0: michael@0: // static michael@0: nsresult michael@0: nsRange::CompareNodeToRange(nsINode* aNode, nsRange* aRange, michael@0: bool *outNodeBefore, bool *outNodeAfter) michael@0: { michael@0: NS_ENSURE_STATE(aNode); michael@0: // create a pair of dom points that expresses location of node: michael@0: // NODE(start), NODE(end) michael@0: // Let incoming range be: michael@0: // {RANGE(start), RANGE(end)} michael@0: // if (RANGE(start) <= NODE(start)) and (RANGE(end) => NODE(end)) michael@0: // then the Node is contained (completely) by the Range. michael@0: michael@0: if (!aRange || !aRange->IsPositioned()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // gather up the dom point info michael@0: int32_t nodeStart, nodeEnd; michael@0: nsINode* parent = aNode->GetParentNode(); michael@0: if (!parent) { michael@0: // can't make a parent/offset pair to represent start or michael@0: // end of the root node, because it has no parent. michael@0: // so instead represent it by (node,0) and (node,numChildren) michael@0: parent = aNode; michael@0: nodeStart = 0; michael@0: nodeEnd = aNode->GetChildCount(); michael@0: } michael@0: else { michael@0: nodeStart = parent->IndexOf(aNode); michael@0: nodeEnd = nodeStart + 1; michael@0: } michael@0: michael@0: nsINode* rangeStartParent = aRange->GetStartParent(); michael@0: nsINode* rangeEndParent = aRange->GetEndParent(); michael@0: int32_t rangeStartOffset = aRange->StartOffset(); michael@0: int32_t rangeEndOffset = aRange->EndOffset(); michael@0: michael@0: // is RANGE(start) <= NODE(start) ? michael@0: bool disconnected = false; michael@0: *outNodeBefore = nsContentUtils::ComparePoints(rangeStartParent, michael@0: rangeStartOffset, michael@0: parent, nodeStart, michael@0: &disconnected) > 0; michael@0: NS_ENSURE_TRUE(!disconnected, NS_ERROR_DOM_WRONG_DOCUMENT_ERR); michael@0: michael@0: // is RANGE(end) >= NODE(end) ? michael@0: *outNodeAfter = nsContentUtils::ComparePoints(rangeEndParent, michael@0: rangeEndOffset, michael@0: parent, nodeEnd, michael@0: &disconnected) < 0; michael@0: NS_ENSURE_TRUE(!disconnected, NS_ERROR_DOM_WRONG_DOCUMENT_ERR); michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct FindSelectedRangeData michael@0: { michael@0: nsINode* mNode; michael@0: nsRange* mResult; michael@0: uint32_t mStartOffset; michael@0: uint32_t mEndOffset; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: FindSelectedRange(nsPtrHashKey* aEntry, void* userArg) michael@0: { michael@0: nsRange* range = aEntry->GetKey(); michael@0: if (range->IsInSelection() && !range->Collapsed()) { michael@0: FindSelectedRangeData* data = static_cast(userArg); michael@0: int32_t cmp = nsContentUtils::ComparePoints(data->mNode, data->mEndOffset, michael@0: range->GetStartParent(), michael@0: range->StartOffset()); michael@0: if (cmp == 1) { michael@0: cmp = nsContentUtils::ComparePoints(data->mNode, data->mStartOffset, michael@0: range->GetEndParent(), michael@0: range->EndOffset()); michael@0: if (cmp == -1) { michael@0: data->mResult = range; michael@0: return PL_DHASH_STOP; michael@0: } michael@0: } michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static nsINode* michael@0: GetNextRangeCommonAncestor(nsINode* aNode) michael@0: { michael@0: while (aNode && !aNode->IsCommonAncestorForRangeInSelection()) { michael@0: if (!aNode->IsDescendantOfCommonAncestorForRangeInSelection()) { michael@0: return nullptr; michael@0: } michael@0: aNode = aNode->GetParentNode(); michael@0: } michael@0: return aNode; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsRange::IsNodeSelected(nsINode* aNode, uint32_t aStartOffset, michael@0: uint32_t aEndOffset) michael@0: { michael@0: NS_PRECONDITION(aNode, "bad arg"); michael@0: michael@0: FindSelectedRangeData data = { aNode, nullptr, aStartOffset, aEndOffset }; michael@0: nsINode* n = GetNextRangeCommonAncestor(aNode); michael@0: NS_ASSERTION(n || !aNode->IsSelectionDescendant(), michael@0: "orphan selection descendant"); michael@0: for (; n; n = GetNextRangeCommonAncestor(n->GetParentNode())) { michael@0: RangeHashTable* ranges = michael@0: static_cast(n->GetProperty(nsGkAtoms::range)); michael@0: ranges->EnumerateEntries(FindSelectedRange, &data); michael@0: if (data.mResult) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /****************************************************** michael@0: * constructor/destructor michael@0: ******************************************************/ michael@0: michael@0: nsRange::~nsRange() michael@0: { michael@0: NS_ASSERTION(!IsInSelection(), "deleting nsRange that is in use"); michael@0: michael@0: // Maybe we can remove Detach() -- bug 702948. michael@0: Telemetry::Accumulate(Telemetry::DOM_RANGE_DETACHED, mIsDetached); michael@0: michael@0: // we want the side effects (releases and list removals) michael@0: DoSetRange(nullptr, 0, nullptr, 0, nullptr); michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: nsRange::CreateRange(nsINode* aStartParent, int32_t aStartOffset, michael@0: nsINode* aEndParent, int32_t aEndOffset, michael@0: nsRange** aRange) michael@0: { michael@0: nsCOMPtr startDomNode = do_QueryInterface(aStartParent); michael@0: nsCOMPtr endDomNode = do_QueryInterface(aEndParent); michael@0: michael@0: nsresult rv = CreateRange(startDomNode, aStartOffset, endDomNode, aEndOffset, michael@0: aRange); michael@0: michael@0: return rv; michael@0: michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: nsRange::CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset, michael@0: nsIDOMNode* aEndParent, int32_t aEndOffset, michael@0: nsRange** aRange) michael@0: { michael@0: MOZ_ASSERT(aRange); michael@0: *aRange = nullptr; michael@0: michael@0: nsCOMPtr startParent = do_QueryInterface(aStartParent); michael@0: NS_ENSURE_ARG_POINTER(startParent); michael@0: michael@0: nsRefPtr range = new nsRange(startParent); michael@0: michael@0: nsresult rv = range->SetStart(startParent, aStartOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = range->SetEnd(aEndParent, aEndOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: range.forget(aRange); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: nsRange::CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset, michael@0: nsIDOMNode* aEndParent, int32_t aEndOffset, michael@0: nsIDOMRange** aRange) michael@0: { michael@0: nsRefPtr range; michael@0: nsresult rv = nsRange::CreateRange(aStartParent, aStartOffset, aEndParent, michael@0: aEndOffset, getter_AddRefs(range)); michael@0: range.forget(aRange); michael@0: return rv; michael@0: } michael@0: michael@0: /****************************************************** michael@0: * nsISupports michael@0: ******************************************************/ michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsRange) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsRange, michael@0: DoSetRange(nullptr, 0, nullptr, 0, nullptr)) michael@0: michael@0: // QueryInterface implementation for nsRange michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsRange) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMRange) michael@0: NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMRange) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsRange) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsRange) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner); michael@0: tmp->Reset(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsRange) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartParent) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndParent) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsRange) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: static void MarkDescendants(nsINode* aNode) michael@0: { michael@0: // Set NodeIsDescendantOfCommonAncestorForRangeInSelection on aNode's michael@0: // descendants unless aNode is already marked as a range common ancestor michael@0: // or a descendant of one, in which case all of our descendants have the michael@0: // bit set already. michael@0: if (!aNode->IsSelectionDescendant()) { michael@0: // don't set the Descendant bit on |aNode| itself michael@0: nsINode* node = aNode->GetNextNode(aNode); michael@0: while (node) { michael@0: node->SetDescendantOfCommonAncestorForRangeInSelection(); michael@0: if (!node->IsCommonAncestorForRangeInSelection()) { michael@0: node = node->GetNextNode(aNode); michael@0: } else { michael@0: // optimize: skip this sub-tree since it's marked already. michael@0: node = node->GetNextNonChildNode(aNode); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void UnmarkDescendants(nsINode* aNode) michael@0: { michael@0: // Unset NodeIsDescendantOfCommonAncestorForRangeInSelection on aNode's michael@0: // descendants unless aNode is a descendant of another range common ancestor. michael@0: // Also, exclude descendants of range common ancestors (but not the common michael@0: // ancestor itself). michael@0: if (!aNode->IsDescendantOfCommonAncestorForRangeInSelection()) { michael@0: // we know |aNode| doesn't have any bit set michael@0: nsINode* node = aNode->GetNextNode(aNode); michael@0: while (node) { michael@0: node->ClearDescendantOfCommonAncestorForRangeInSelection(); michael@0: if (!node->IsCommonAncestorForRangeInSelection()) { michael@0: node = node->GetNextNode(aNode); michael@0: } else { michael@0: // We found an ancestor of an overlapping range, skip its descendants. michael@0: node = node->GetNextNonChildNode(aNode); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRange::RegisterCommonAncestor(nsINode* aNode) michael@0: { michael@0: NS_PRECONDITION(aNode, "bad arg"); michael@0: NS_ASSERTION(IsInSelection(), "registering range not in selection"); michael@0: michael@0: MarkDescendants(aNode); michael@0: michael@0: RangeHashTable* ranges = michael@0: static_cast(aNode->GetProperty(nsGkAtoms::range)); michael@0: if (!ranges) { michael@0: ranges = new RangeHashTable; michael@0: aNode->SetProperty(nsGkAtoms::range, ranges, michael@0: nsINode::DeleteProperty, true); michael@0: } michael@0: ranges->PutEntry(this); michael@0: aNode->SetCommonAncestorForRangeInSelection(); michael@0: } michael@0: michael@0: void michael@0: nsRange::UnregisterCommonAncestor(nsINode* aNode) michael@0: { michael@0: NS_PRECONDITION(aNode, "bad arg"); michael@0: NS_ASSERTION(aNode->IsCommonAncestorForRangeInSelection(), "wrong node"); michael@0: RangeHashTable* ranges = michael@0: static_cast(aNode->GetProperty(nsGkAtoms::range)); michael@0: NS_ASSERTION(ranges->GetEntry(this), "unknown range"); michael@0: michael@0: if (ranges->Count() == 1) { michael@0: aNode->ClearCommonAncestorForRangeInSelection(); michael@0: aNode->DeleteProperty(nsGkAtoms::range); michael@0: UnmarkDescendants(aNode); michael@0: } else { michael@0: ranges->RemoveEntry(this); michael@0: } michael@0: } michael@0: michael@0: /****************************************************** michael@0: * nsIMutationObserver implementation michael@0: ******************************************************/ michael@0: michael@0: void michael@0: nsRange::CharacterDataChanged(nsIDocument* aDocument, michael@0: nsIContent* aContent, michael@0: CharacterDataChangeInfo* aInfo) michael@0: { michael@0: MOZ_ASSERT(mAssertNextInsertOrAppendIndex == -1, michael@0: "splitText failed to notify insert/append?"); michael@0: NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned"); michael@0: michael@0: nsINode* newRoot = nullptr; michael@0: nsINode* newStartNode = nullptr; michael@0: nsINode* newEndNode = nullptr; michael@0: uint32_t newStartOffset = 0; michael@0: uint32_t newEndOffset = 0; michael@0: michael@0: if (aInfo->mDetails && michael@0: aInfo->mDetails->mType == CharacterDataChangeInfo::Details::eSplit) { michael@0: // If the splitted text node is immediately before a range boundary point michael@0: // that refers to a child index (i.e. its parent is the boundary container) michael@0: // then we need to increment the corresponding offset to account for the new michael@0: // text node that will be inserted. If so, we need to prevent the next michael@0: // ContentInserted or ContentAppended for this range from incrementing it michael@0: // again (when the new text node is notified). michael@0: nsINode* parentNode = aContent->GetParentNode(); michael@0: int32_t index = -1; michael@0: if (parentNode == mEndParent && mEndOffset > 0 && michael@0: (index = parentNode->IndexOf(aContent)) + 1 == mEndOffset) { michael@0: ++mEndOffset; michael@0: mEndOffsetWasIncremented = true; michael@0: } michael@0: if (parentNode == mStartParent && mStartOffset > 0 && michael@0: (index != -1 ? index : parentNode->IndexOf(aContent)) + 1 == mStartOffset) { michael@0: ++mStartOffset; michael@0: mStartOffsetWasIncremented = true; michael@0: } michael@0: #ifdef DEBUG michael@0: if (mStartOffsetWasIncremented || mEndOffsetWasIncremented) { michael@0: mAssertNextInsertOrAppendIndex = michael@0: (mStartOffsetWasIncremented ? mStartOffset : mEndOffset) - 1; michael@0: mAssertNextInsertOrAppendNode = aInfo->mDetails->mNextSibling; michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // If the changed node contains our start boundary and the change starts michael@0: // before the boundary we'll need to adjust the offset. michael@0: if (aContent == mStartParent && michael@0: aInfo->mChangeStart < static_cast(mStartOffset)) { michael@0: if (aInfo->mDetails) { michael@0: // splitText(), aInfo->mDetails->mNextSibling is the new text node michael@0: NS_ASSERTION(aInfo->mDetails->mType == michael@0: CharacterDataChangeInfo::Details::eSplit, michael@0: "only a split can start before the end"); michael@0: NS_ASSERTION(static_cast(mStartOffset) <= aInfo->mChangeEnd + 1, michael@0: "mStartOffset is beyond the end of this node"); michael@0: newStartOffset = static_cast(mStartOffset) - aInfo->mChangeStart; michael@0: newStartNode = aInfo->mDetails->mNextSibling; michael@0: if (MOZ_UNLIKELY(aContent == mRoot)) { michael@0: newRoot = IsValidBoundary(newStartNode); michael@0: } michael@0: michael@0: bool isCommonAncestor = IsInSelection() && mStartParent == mEndParent; michael@0: if (isCommonAncestor) { michael@0: UnregisterCommonAncestor(mStartParent); michael@0: RegisterCommonAncestor(newStartNode); michael@0: } michael@0: if (mStartParent->IsDescendantOfCommonAncestorForRangeInSelection()) { michael@0: newStartNode->SetDescendantOfCommonAncestorForRangeInSelection(); michael@0: } michael@0: } else { michael@0: // If boundary is inside changed text, position it before change michael@0: // else adjust start offset for the change in length. michael@0: mStartOffset = static_cast(mStartOffset) <= aInfo->mChangeEnd ? michael@0: aInfo->mChangeStart : michael@0: mStartOffset + aInfo->mChangeStart - aInfo->mChangeEnd + michael@0: aInfo->mReplaceLength; michael@0: } michael@0: } michael@0: michael@0: // Do the same thing for the end boundary, except for splitText of a node michael@0: // with no parent then only switch to the new node if the start boundary michael@0: // did so too (otherwise the range would end up with disconnected nodes). michael@0: if (aContent == mEndParent && michael@0: aInfo->mChangeStart < static_cast(mEndOffset)) { michael@0: if (aInfo->mDetails && (aContent->GetParentNode() || newStartNode)) { michael@0: // splitText(), aInfo->mDetails->mNextSibling is the new text node michael@0: NS_ASSERTION(aInfo->mDetails->mType == michael@0: CharacterDataChangeInfo::Details::eSplit, michael@0: "only a split can start before the end"); michael@0: NS_ASSERTION(static_cast(mEndOffset) <= aInfo->mChangeEnd + 1, michael@0: "mEndOffset is beyond the end of this node"); michael@0: newEndOffset = static_cast(mEndOffset) - aInfo->mChangeStart; michael@0: newEndNode = aInfo->mDetails->mNextSibling; michael@0: michael@0: bool isCommonAncestor = IsInSelection() && mStartParent == mEndParent; michael@0: if (isCommonAncestor && !newStartNode) { michael@0: // The split occurs inside the range. michael@0: UnregisterCommonAncestor(mStartParent); michael@0: RegisterCommonAncestor(mStartParent->GetParentNode()); michael@0: newEndNode->SetDescendantOfCommonAncestorForRangeInSelection(); michael@0: } else if (mEndParent->IsDescendantOfCommonAncestorForRangeInSelection()) { michael@0: newEndNode->SetDescendantOfCommonAncestorForRangeInSelection(); michael@0: } michael@0: } else { michael@0: mEndOffset = static_cast(mEndOffset) <= aInfo->mChangeEnd ? michael@0: aInfo->mChangeStart : michael@0: mEndOffset + aInfo->mChangeStart - aInfo->mChangeEnd + michael@0: aInfo->mReplaceLength; michael@0: } michael@0: } michael@0: michael@0: if (aInfo->mDetails && michael@0: aInfo->mDetails->mType == CharacterDataChangeInfo::Details::eMerge) { michael@0: // normalize(), aInfo->mDetails->mNextSibling is the merged text node michael@0: // that will be removed michael@0: nsIContent* removed = aInfo->mDetails->mNextSibling; michael@0: if (removed == mStartParent) { michael@0: newStartOffset = static_cast(mStartOffset) + aInfo->mChangeStart; michael@0: newStartNode = aContent; michael@0: if (MOZ_UNLIKELY(removed == mRoot)) { michael@0: newRoot = IsValidBoundary(newStartNode); michael@0: } michael@0: } michael@0: if (removed == mEndParent) { michael@0: newEndOffset = static_cast(mEndOffset) + aInfo->mChangeStart; michael@0: newEndNode = aContent; michael@0: if (MOZ_UNLIKELY(removed == mRoot)) { michael@0: newRoot = IsValidBoundary(newEndNode); michael@0: } michael@0: } michael@0: // When the removed text node's parent is one of our boundary nodes we may michael@0: // need to adjust the offset to account for the removed node. However, michael@0: // there will also be a ContentRemoved notification later so the only cases michael@0: // we need to handle here is when the removed node is the text node after michael@0: // the boundary. (The m*Offset > 0 check is an optimization - a boundary michael@0: // point before the first child is never affected by normalize().) michael@0: nsINode* parentNode = aContent->GetParentNode(); michael@0: if (parentNode == mStartParent && mStartOffset > 0 && michael@0: uint32_t(mStartOffset) < parentNode->GetChildCount() && michael@0: removed == parentNode->GetChildAt(mStartOffset)) { michael@0: newStartNode = aContent; michael@0: newStartOffset = aInfo->mChangeStart; michael@0: } michael@0: if (parentNode == mEndParent && mEndOffset > 0 && michael@0: uint32_t(mEndOffset) < parentNode->GetChildCount() && michael@0: removed == parentNode->GetChildAt(mEndOffset)) { michael@0: newEndNode = aContent; michael@0: newEndOffset = aInfo->mChangeEnd; michael@0: } michael@0: } michael@0: michael@0: if (newStartNode || newEndNode) { michael@0: if (!newStartNode) { michael@0: newStartNode = mStartParent; michael@0: newStartOffset = mStartOffset; michael@0: } michael@0: if (!newEndNode) { michael@0: newEndNode = mEndParent; michael@0: newEndOffset = mEndOffset; michael@0: } michael@0: DoSetRange(newStartNode, newStartOffset, newEndNode, newEndOffset, michael@0: newRoot ? newRoot : mRoot.get(), michael@0: !newEndNode->GetParentNode() || !newStartNode->GetParentNode()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRange::ContentAppended(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t aNewIndexInContainer) michael@0: { michael@0: NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned"); michael@0: michael@0: nsINode* container = NODE_FROM(aContainer, aDocument); michael@0: if (container->IsSelectionDescendant() && IsInSelection()) { michael@0: nsINode* child = aFirstNewContent; michael@0: while (child) { michael@0: if (!child->IsDescendantOfCommonAncestorForRangeInSelection()) { michael@0: MarkDescendants(child); michael@0: child->SetDescendantOfCommonAncestorForRangeInSelection(); michael@0: } michael@0: child = child->GetNextSibling(); michael@0: } michael@0: } michael@0: michael@0: if (mStartOffsetWasIncremented || mEndOffsetWasIncremented) { michael@0: MOZ_ASSERT(mAssertNextInsertOrAppendIndex == aNewIndexInContainer); michael@0: MOZ_ASSERT(mAssertNextInsertOrAppendNode == aFirstNewContent); michael@0: MOZ_ASSERT(aFirstNewContent->IsNodeOfType(nsINode::eDATA_NODE)); michael@0: mStartOffsetWasIncremented = mEndOffsetWasIncremented = false; michael@0: #ifdef DEBUG michael@0: mAssertNextInsertOrAppendIndex = -1; michael@0: mAssertNextInsertOrAppendNode = nullptr; michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRange::ContentInserted(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned"); michael@0: michael@0: nsINode* container = NODE_FROM(aContainer, aDocument); michael@0: michael@0: // Adjust position if a sibling was inserted. michael@0: if (container == mStartParent && aIndexInContainer < mStartOffset && michael@0: !mStartOffsetWasIncremented) { michael@0: ++mStartOffset; michael@0: } michael@0: if (container == mEndParent && aIndexInContainer < mEndOffset && michael@0: !mEndOffsetWasIncremented) { michael@0: ++mEndOffset; michael@0: } michael@0: if (container->IsSelectionDescendant() && michael@0: !aChild->IsDescendantOfCommonAncestorForRangeInSelection()) { michael@0: MarkDescendants(aChild); michael@0: aChild->SetDescendantOfCommonAncestorForRangeInSelection(); michael@0: } michael@0: michael@0: if (mStartOffsetWasIncremented || mEndOffsetWasIncremented) { michael@0: MOZ_ASSERT(mAssertNextInsertOrAppendIndex == aIndexInContainer); michael@0: MOZ_ASSERT(mAssertNextInsertOrAppendNode == aChild); michael@0: MOZ_ASSERT(aChild->IsNodeOfType(nsINode::eDATA_NODE)); michael@0: mStartOffsetWasIncremented = mEndOffsetWasIncremented = false; michael@0: #ifdef DEBUG michael@0: mAssertNextInsertOrAppendIndex = -1; michael@0: mAssertNextInsertOrAppendNode = nullptr; michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRange::ContentRemoved(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned"); michael@0: MOZ_ASSERT(!mStartOffsetWasIncremented && !mEndOffsetWasIncremented && michael@0: mAssertNextInsertOrAppendIndex == -1, michael@0: "splitText failed to notify insert/append?"); michael@0: michael@0: nsINode* container = NODE_FROM(aContainer, aDocument); michael@0: bool gravitateStart = false; michael@0: bool gravitateEnd = false; michael@0: bool didCheckStartParentDescendant = false; michael@0: michael@0: // Adjust position if a sibling was removed... michael@0: if (container == mStartParent) { michael@0: if (aIndexInContainer < mStartOffset) { michael@0: --mStartOffset; michael@0: } michael@0: } else { // ...or gravitate if an ancestor was removed. michael@0: didCheckStartParentDescendant = true; michael@0: gravitateStart = nsContentUtils::ContentIsDescendantOf(mStartParent, aChild); michael@0: } michael@0: michael@0: // Do same thing for end boundry. michael@0: if (container == mEndParent) { michael@0: if (aIndexInContainer < mEndOffset) { michael@0: --mEndOffset; michael@0: } michael@0: } else if (didCheckStartParentDescendant && mStartParent == mEndParent) { michael@0: gravitateEnd = gravitateStart; michael@0: } else { michael@0: gravitateEnd = nsContentUtils::ContentIsDescendantOf(mEndParent, aChild); michael@0: } michael@0: michael@0: if (!mEnableGravitationOnElementRemoval) { michael@0: // Do not gravitate. michael@0: return; michael@0: } michael@0: michael@0: if (gravitateStart || gravitateEnd) { michael@0: DoSetRange(gravitateStart ? container : mStartParent.get(), michael@0: gravitateStart ? aIndexInContainer : mStartOffset, michael@0: gravitateEnd ? container : mEndParent.get(), michael@0: gravitateEnd ? aIndexInContainer : mEndOffset, michael@0: mRoot); michael@0: } michael@0: if (container->IsSelectionDescendant() && michael@0: aChild->IsDescendantOfCommonAncestorForRangeInSelection()) { michael@0: aChild->ClearDescendantOfCommonAncestorForRangeInSelection(); michael@0: UnmarkDescendants(aChild); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRange::ParentChainChanged(nsIContent *aContent) michael@0: { michael@0: MOZ_ASSERT(!mStartOffsetWasIncremented && !mEndOffsetWasIncremented && michael@0: mAssertNextInsertOrAppendIndex == -1, michael@0: "splitText failed to notify insert/append?"); michael@0: NS_ASSERTION(mRoot == aContent, "Wrong ParentChainChanged notification?"); michael@0: nsINode* newRoot = IsValidBoundary(mStartParent); michael@0: NS_ASSERTION(newRoot, "No valid boundary or root found!"); michael@0: if (newRoot != IsValidBoundary(mEndParent)) { michael@0: // Sometimes ordering involved in cycle collection can lead to our michael@0: // start parent and/or end parent being disconnected from our root michael@0: // without our getting a ContentRemoved notification. michael@0: // See bug 846096 for more details. michael@0: NS_ASSERTION(mEndParent->IsInNativeAnonymousSubtree(), michael@0: "This special case should happen only with " michael@0: "native-anonymous content"); michael@0: // When that happens, bail out and set pointers to null; since we're michael@0: // in cycle collection and unreachable it shouldn't matter. michael@0: Reset(); michael@0: return; michael@0: } michael@0: // This is safe without holding a strong ref to self as long as the change michael@0: // of mRoot is the last thing in DoSetRange. michael@0: DoSetRange(mStartParent, mStartOffset, mEndParent, mEndOffset, newRoot); michael@0: } michael@0: michael@0: /****************************************************** michael@0: * Utilities for comparing points: API from nsIDOMRange michael@0: ******************************************************/ michael@0: NS_IMETHODIMP michael@0: nsRange::IsPointInRange(nsIDOMNode* aParent, int32_t aOffset, bool* aResult) michael@0: { michael@0: nsCOMPtr parent = do_QueryInterface(aParent); michael@0: if (!parent) { michael@0: return NS_ERROR_DOM_NOT_OBJECT_ERR; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: *aResult = IsPointInRange(*parent, aOffset, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: bool michael@0: nsRange::IsPointInRange(nsINode& aParent, uint32_t aOffset, ErrorResult& aRv) michael@0: { michael@0: uint16_t compareResult = ComparePoint(aParent, aOffset, aRv); michael@0: // If the node isn't in the range's document, it clearly isn't in the range. michael@0: if (aRv.ErrorCode() == NS_ERROR_DOM_WRONG_DOCUMENT_ERR) { michael@0: aRv = NS_OK; michael@0: return false; michael@0: } michael@0: michael@0: return compareResult == 0; michael@0: } michael@0: michael@0: // returns -1 if point is before range, 0 if point is in range, michael@0: // 1 if point is after range. michael@0: NS_IMETHODIMP michael@0: nsRange::ComparePoint(nsIDOMNode* aParent, int32_t aOffset, int16_t* aResult) michael@0: { michael@0: nsCOMPtr parent = do_QueryInterface(aParent); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_DOM_HIERARCHY_REQUEST_ERR); michael@0: michael@0: ErrorResult rv; michael@0: *aResult = ComparePoint(*parent, aOffset, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: int16_t michael@0: nsRange::ComparePoint(nsINode& aParent, uint32_t aOffset, ErrorResult& aRv) michael@0: { michael@0: // our range is in a good state? michael@0: if (!mIsPositioned) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); michael@0: return 0; michael@0: } michael@0: michael@0: if (!nsContentUtils::ContentIsDescendantOf(&aParent, mRoot)) { michael@0: aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR); michael@0: return 0; michael@0: } michael@0: michael@0: if (aParent.NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); michael@0: return 0; michael@0: } michael@0: michael@0: if (aOffset > aParent.Length()) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return 0; michael@0: } michael@0: michael@0: int32_t cmp; michael@0: if ((cmp = nsContentUtils::ComparePoints(&aParent, aOffset, michael@0: mStartParent, mStartOffset)) <= 0) { michael@0: michael@0: return cmp; michael@0: } michael@0: if (nsContentUtils::ComparePoints(mEndParent, mEndOffset, michael@0: &aParent, aOffset) == -1) { michael@0: return 1; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::IntersectsNode(nsIDOMNode* aNode, bool* aResult) michael@0: { michael@0: *aResult = false; michael@0: michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: // TODO: This should throw a TypeError. michael@0: NS_ENSURE_ARG(node); michael@0: michael@0: ErrorResult rv; michael@0: *aResult = IntersectsNode(*node, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: bool michael@0: nsRange::IntersectsNode(nsINode& aNode, ErrorResult& aRv) michael@0: { michael@0: if (!mIsPositioned) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); michael@0: return false; michael@0: } michael@0: michael@0: // Step 3. michael@0: nsINode* parent = aNode.GetParentNode(); michael@0: if (!parent) { michael@0: // Steps 2 and 4. michael@0: // |parent| is null, so |node|'s root is |node| itself. michael@0: return GetRoot() == &aNode; michael@0: } michael@0: michael@0: // Step 5. michael@0: int32_t nodeIndex = parent->IndexOf(&aNode); michael@0: michael@0: // Steps 6-7. michael@0: // Note: if disconnected is true, ComparePoints returns 1. michael@0: bool disconnected = false; michael@0: bool result = nsContentUtils::ComparePoints(mStartParent, mStartOffset, michael@0: parent, nodeIndex + 1, michael@0: &disconnected) < 0 && michael@0: nsContentUtils::ComparePoints(parent, nodeIndex, michael@0: mEndParent, mEndOffset, michael@0: &disconnected) < 0; michael@0: michael@0: // Step 2. michael@0: if (disconnected) { michael@0: result = false; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /****************************************************** michael@0: * Private helper routines michael@0: ******************************************************/ michael@0: michael@0: // It's important that all setting of the range start/end points michael@0: // go through this function, which will do all the right voodoo michael@0: // for content notification of range ownership. michael@0: // Calling DoSetRange with either parent argument null will collapse michael@0: // the range to have both endpoints point to the other node michael@0: void michael@0: nsRange::DoSetRange(nsINode* aStartN, int32_t aStartOffset, michael@0: nsINode* aEndN, int32_t aEndOffset, michael@0: nsINode* aRoot, bool aNotInsertedYet) michael@0: { michael@0: NS_PRECONDITION((aStartN && aEndN && aRoot) || michael@0: (!aStartN && !aEndN && !aRoot), michael@0: "Set all or none"); michael@0: NS_PRECONDITION(!aRoot || aNotInsertedYet || michael@0: (nsContentUtils::ContentIsDescendantOf(aStartN, aRoot) && michael@0: nsContentUtils::ContentIsDescendantOf(aEndN, aRoot) && michael@0: aRoot == IsValidBoundary(aStartN) && michael@0: aRoot == IsValidBoundary(aEndN)), michael@0: "Wrong root"); michael@0: NS_PRECONDITION(!aRoot || michael@0: (aStartN->IsNodeOfType(nsINode::eCONTENT) && michael@0: aEndN->IsNodeOfType(nsINode::eCONTENT) && michael@0: aRoot == michael@0: static_cast(aStartN)->GetBindingParent() && michael@0: aRoot == michael@0: static_cast(aEndN)->GetBindingParent()) || michael@0: (!aRoot->GetParentNode() && michael@0: (aRoot->IsNodeOfType(nsINode::eDOCUMENT) || michael@0: aRoot->IsNodeOfType(nsINode::eATTRIBUTE) || michael@0: aRoot->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT) || michael@0: /*For backward compatibility*/ michael@0: aRoot->IsNodeOfType(nsINode::eCONTENT))), michael@0: "Bad root"); michael@0: michael@0: if (mRoot != aRoot) { michael@0: if (mRoot) { michael@0: mRoot->RemoveMutationObserver(this); michael@0: } michael@0: if (aRoot) { michael@0: aRoot->AddMutationObserver(this); michael@0: } michael@0: } michael@0: bool checkCommonAncestor = (mStartParent != aStartN || mEndParent != aEndN) && michael@0: IsInSelection() && !aNotInsertedYet; michael@0: nsINode* oldCommonAncestor = checkCommonAncestor ? GetCommonAncestor() : nullptr; michael@0: mStartParent = aStartN; michael@0: mStartOffset = aStartOffset; michael@0: mEndParent = aEndN; michael@0: mEndOffset = aEndOffset; michael@0: mIsPositioned = !!mStartParent; michael@0: if (checkCommonAncestor) { michael@0: nsINode* newCommonAncestor = GetCommonAncestor(); michael@0: if (newCommonAncestor != oldCommonAncestor) { michael@0: if (oldCommonAncestor) { michael@0: UnregisterCommonAncestor(oldCommonAncestor); michael@0: } michael@0: if (newCommonAncestor) { michael@0: RegisterCommonAncestor(newCommonAncestor); michael@0: } else { michael@0: NS_ASSERTION(!mIsPositioned, "unexpected disconnected nodes"); michael@0: mInSelection = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // This needs to be the last thing this function does. See comment michael@0: // in ParentChainChanged. michael@0: mRoot = aRoot; michael@0: } michael@0: michael@0: static int32_t michael@0: IndexOf(nsINode* aChild) michael@0: { michael@0: nsINode* parent = aChild->GetParentNode(); michael@0: michael@0: return parent ? parent->IndexOf(aChild) : -1; michael@0: } michael@0: michael@0: nsINode* michael@0: nsRange::GetCommonAncestor() const michael@0: { michael@0: return mIsPositioned ? michael@0: nsContentUtils::GetCommonAncestor(mStartParent, mEndParent) : michael@0: nullptr; michael@0: } michael@0: michael@0: void michael@0: nsRange::Reset() michael@0: { michael@0: DoSetRange(nullptr, 0, nullptr, 0, nullptr); michael@0: } michael@0: michael@0: /****************************************************** michael@0: * public functionality michael@0: ******************************************************/ michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::GetStartContainer(nsIDOMNode** aStartParent) michael@0: { michael@0: if (!mIsPositioned) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: return CallQueryInterface(mStartParent, aStartParent); michael@0: } michael@0: michael@0: nsINode* michael@0: nsRange::GetStartContainer(ErrorResult& aRv) const michael@0: { michael@0: if (!mIsPositioned) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); michael@0: return nullptr; michael@0: } michael@0: michael@0: return mStartParent; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::GetStartOffset(int32_t* aStartOffset) michael@0: { michael@0: if (!mIsPositioned) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: *aStartOffset = mStartOffset; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t michael@0: nsRange::GetStartOffset(ErrorResult& aRv) const michael@0: { michael@0: if (!mIsPositioned) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); michael@0: return 0; michael@0: } michael@0: michael@0: return mStartOffset; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::GetEndContainer(nsIDOMNode** aEndParent) michael@0: { michael@0: if (!mIsPositioned) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: return CallQueryInterface(mEndParent, aEndParent); michael@0: } michael@0: michael@0: nsINode* michael@0: nsRange::GetEndContainer(ErrorResult& aRv) const michael@0: { michael@0: if (!mIsPositioned) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); michael@0: return nullptr; michael@0: } michael@0: michael@0: return mEndParent; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::GetEndOffset(int32_t* aEndOffset) michael@0: { michael@0: if (!mIsPositioned) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: *aEndOffset = mEndOffset; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t michael@0: nsRange::GetEndOffset(ErrorResult& aRv) const michael@0: { michael@0: if (!mIsPositioned) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); michael@0: return 0; michael@0: } michael@0: michael@0: return mEndOffset; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::GetCollapsed(bool* aIsCollapsed) michael@0: { michael@0: if (!mIsPositioned) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: *aIsCollapsed = Collapsed(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsINode* michael@0: nsRange::GetCommonAncestorContainer(ErrorResult& aRv) const michael@0: { michael@0: if (!mIsPositioned) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); michael@0: return nullptr; michael@0: } michael@0: michael@0: return nsContentUtils::GetCommonAncestor(mStartParent, mEndParent); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::GetCommonAncestorContainer(nsIDOMNode** aCommonParent) michael@0: { michael@0: ErrorResult rv; michael@0: nsINode* commonAncestor = GetCommonAncestorContainer(rv); michael@0: if (commonAncestor) { michael@0: NS_ADDREF(*aCommonParent = commonAncestor->AsDOMNode()); michael@0: } else { michael@0: *aCommonParent = nullptr; michael@0: } michael@0: michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: nsINode* michael@0: nsRange::IsValidBoundary(nsINode* aNode) michael@0: { michael@0: if (!aNode) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (aNode->IsNodeOfType(nsINode::eCONTENT)) { michael@0: nsIContent* content = static_cast(aNode); michael@0: if (content->Tag() == nsGkAtoms::documentTypeNodeName) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!mMaySpanAnonymousSubtrees) { michael@0: // If the node is in a shadow tree then the ShadowRoot is the root. michael@0: ShadowRoot* containingShadow = content->GetContainingShadow(); michael@0: if (containingShadow) { michael@0: return containingShadow; michael@0: } michael@0: michael@0: // If the node has a binding parent, that should be the root. michael@0: // XXXbz maybe only for native anonymous content? michael@0: nsINode* root = content->GetBindingParent(); michael@0: if (root) { michael@0: return root; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Elements etc. must be in document or in document fragment, michael@0: // text nodes in document, in document fragment or in attribute. michael@0: nsINode* root = aNode->GetCurrentDoc(); michael@0: if (root) { michael@0: return root; michael@0: } michael@0: michael@0: root = aNode->SubtreeRoot(); michael@0: michael@0: NS_ASSERTION(!root->IsNodeOfType(nsINode::eDOCUMENT), michael@0: "GetCurrentDoc should have returned a doc"); michael@0: michael@0: // We allow this because of backward compatibility. michael@0: return root; michael@0: } michael@0: michael@0: void michael@0: nsRange::SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) michael@0: { michael@0: if (!nsContentUtils::CanCallerAccess(&aNode)) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: AutoInvalidateSelection atEndOfBlock(this); michael@0: aRv = SetStart(&aNode, aOffset); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::SetStart(nsIDOMNode* aParent, int32_t aOffset) michael@0: { michael@0: nsCOMPtr parent = do_QueryInterface(aParent); michael@0: if (!parent) { michael@0: return NS_ERROR_DOM_NOT_OBJECT_ERR; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: SetStart(*parent, aOffset, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: /* virtual */ nsresult michael@0: nsRange::SetStart(nsINode* aParent, int32_t aOffset) michael@0: { michael@0: nsINode* newRoot = IsValidBoundary(aParent); michael@0: NS_ENSURE_TRUE(newRoot, NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); michael@0: michael@0: if (aOffset < 0 || uint32_t(aOffset) > aParent->Length()) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: michael@0: // Collapse if not positioned yet, if positioned in another doc or michael@0: // if the new start is after end. michael@0: if (!mIsPositioned || newRoot != mRoot || michael@0: nsContentUtils::ComparePoints(aParent, aOffset, michael@0: mEndParent, mEndOffset) == 1) { michael@0: DoSetRange(aParent, aOffset, aParent, aOffset, newRoot); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: DoSetRange(aParent, aOffset, mEndParent, mEndOffset, mRoot); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv) michael@0: { michael@0: if (!nsContentUtils::CanCallerAccess(&aNode)) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: AutoInvalidateSelection atEndOfBlock(this); michael@0: aRv = SetStart(aNode.GetParentNode(), IndexOf(&aNode)); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::SetStartBefore(nsIDOMNode* aSibling) michael@0: { michael@0: nsCOMPtr sibling = do_QueryInterface(aSibling); michael@0: if (!sibling) { michael@0: return NS_ERROR_DOM_NOT_OBJECT_ERR; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: SetStartBefore(*sibling, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsRange::SetStartAfter(nsINode& aNode, ErrorResult& aRv) michael@0: { michael@0: if (!nsContentUtils::CanCallerAccess(&aNode)) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: AutoInvalidateSelection atEndOfBlock(this); michael@0: aRv = SetStart(aNode.GetParentNode(), IndexOf(&aNode) + 1); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::SetStartAfter(nsIDOMNode* aSibling) michael@0: { michael@0: nsCOMPtr sibling = do_QueryInterface(aSibling); michael@0: if (!sibling) { michael@0: return NS_ERROR_DOM_NOT_OBJECT_ERR; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: SetStartAfter(*sibling, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsRange::SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) michael@0: { michael@0: if (!nsContentUtils::CanCallerAccess(&aNode)) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: AutoInvalidateSelection atEndOfBlock(this); michael@0: aRv = SetEnd(&aNode, aOffset); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::SetEnd(nsIDOMNode* aParent, int32_t aOffset) michael@0: { michael@0: nsCOMPtr parent = do_QueryInterface(aParent); michael@0: if (!parent) { michael@0: return NS_ERROR_DOM_NOT_OBJECT_ERR; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: SetEnd(*parent, aOffset, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: /* virtual */ nsresult michael@0: nsRange::SetEnd(nsINode* aParent, int32_t aOffset) michael@0: { michael@0: nsINode* newRoot = IsValidBoundary(aParent); michael@0: NS_ENSURE_TRUE(newRoot, NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); michael@0: michael@0: if (aOffset < 0 || uint32_t(aOffset) > aParent->Length()) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: michael@0: // Collapse if not positioned yet, if positioned in another doc or michael@0: // if the new end is before start. michael@0: if (!mIsPositioned || newRoot != mRoot || michael@0: nsContentUtils::ComparePoints(mStartParent, mStartOffset, michael@0: aParent, aOffset) == 1) { michael@0: DoSetRange(aParent, aOffset, aParent, aOffset, newRoot); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: DoSetRange(mStartParent, mStartOffset, aParent, aOffset, mRoot); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv) michael@0: { michael@0: if (!nsContentUtils::CanCallerAccess(&aNode)) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: AutoInvalidateSelection atEndOfBlock(this); michael@0: aRv = SetEnd(aNode.GetParentNode(), IndexOf(&aNode)); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::SetEndBefore(nsIDOMNode* aSibling) michael@0: { michael@0: nsCOMPtr sibling = do_QueryInterface(aSibling); michael@0: if (!sibling) { michael@0: return NS_ERROR_DOM_NOT_OBJECT_ERR; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: SetEndBefore(*sibling, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsRange::SetEndAfter(nsINode& aNode, ErrorResult& aRv) michael@0: { michael@0: if (!nsContentUtils::CanCallerAccess(&aNode)) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: AutoInvalidateSelection atEndOfBlock(this); michael@0: aRv = SetEnd(aNode.GetParentNode(), IndexOf(&aNode) + 1); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::SetEndAfter(nsIDOMNode* aSibling) michael@0: { michael@0: nsCOMPtr sibling = do_QueryInterface(aSibling); michael@0: if (!sibling) { michael@0: return NS_ERROR_DOM_NOT_OBJECT_ERR; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: SetEndAfter(*sibling, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::Collapse(bool aToStart) michael@0: { michael@0: if (!mIsPositioned) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: AutoInvalidateSelection atEndOfBlock(this); michael@0: if (aToStart) michael@0: DoSetRange(mStartParent, mStartOffset, mStartParent, mStartOffset, mRoot); michael@0: else michael@0: DoSetRange(mEndParent, mEndOffset, mEndParent, mEndOffset, mRoot); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::SelectNode(nsIDOMNode* aN) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aN); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); michael@0: michael@0: ErrorResult rv; michael@0: SelectNode(*node, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsRange::SelectNode(nsINode& aNode, ErrorResult& aRv) michael@0: { michael@0: if (!nsContentUtils::CanCallerAccess(&aNode)) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsINode* parent = aNode.GetParentNode(); michael@0: nsINode* newRoot = IsValidBoundary(parent); michael@0: if (!newRoot) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); michael@0: return; michael@0: } michael@0: michael@0: int32_t index = parent->IndexOf(&aNode); michael@0: if (index < 0) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); michael@0: return; michael@0: } michael@0: michael@0: AutoInvalidateSelection atEndOfBlock(this); michael@0: DoSetRange(parent, index, parent, index + 1, newRoot); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::SelectNodeContents(nsIDOMNode* aN) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aN); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); michael@0: michael@0: ErrorResult rv; michael@0: SelectNodeContents(*node, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsRange::SelectNodeContents(nsINode& aNode, ErrorResult& aRv) michael@0: { michael@0: if (!nsContentUtils::CanCallerAccess(&aNode)) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsINode* newRoot = IsValidBoundary(&aNode); michael@0: if (!newRoot) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); michael@0: return; michael@0: } michael@0: michael@0: AutoInvalidateSelection atEndOfBlock(this); michael@0: DoSetRange(&aNode, 0, &aNode, aNode.Length(), newRoot); michael@0: } michael@0: michael@0: // The Subtree Content Iterator only returns subtrees that are michael@0: // completely within a given range. It doesn't return a CharacterData michael@0: // node that contains either the start or end point of the range., michael@0: // nor does it return element nodes when nothing in the element is selected. michael@0: // We need an iterator that will also include these start/end points michael@0: // so that our methods/algorithms aren't cluttered with special michael@0: // case code that tries to include these points while iterating. michael@0: // michael@0: // The RangeSubtreeIterator class mimics the nsIContentIterator michael@0: // methods we need, so should the Content Iterator support the michael@0: // start/end points in the future, we can switchover relatively michael@0: // easy. michael@0: michael@0: class MOZ_STACK_CLASS RangeSubtreeIterator michael@0: { michael@0: private: michael@0: michael@0: enum RangeSubtreeIterState { eDone=0, michael@0: eUseStart, michael@0: eUseIterator, michael@0: eUseEnd }; michael@0: michael@0: nsCOMPtr mIter; michael@0: RangeSubtreeIterState mIterState; michael@0: michael@0: nsCOMPtr mStart; michael@0: nsCOMPtr mEnd; michael@0: michael@0: public: michael@0: michael@0: RangeSubtreeIterator() michael@0: : mIterState(eDone) michael@0: { michael@0: } michael@0: ~RangeSubtreeIterator() michael@0: { michael@0: } michael@0: michael@0: nsresult Init(nsRange *aRange); michael@0: already_AddRefed GetCurrentNode(); michael@0: void First(); michael@0: void Last(); michael@0: void Next(); michael@0: void Prev(); michael@0: michael@0: bool IsDone() michael@0: { michael@0: return mIterState == eDone; michael@0: } michael@0: }; michael@0: michael@0: nsresult michael@0: RangeSubtreeIterator::Init(nsRange *aRange) michael@0: { michael@0: mIterState = eDone; michael@0: if (aRange->Collapsed()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Grab the start point of the range and QI it to michael@0: // a CharacterData pointer. If it is CharacterData store michael@0: // a pointer to the node. michael@0: michael@0: ErrorResult rv; michael@0: nsCOMPtr node = aRange->GetStartContainer(rv); michael@0: if (!node) return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr startData = do_QueryInterface(node); michael@0: if (startData || (node->IsElement() && michael@0: node->AsElement()->GetChildCount() == aRange->GetStartOffset(rv))) { michael@0: mStart = node; michael@0: } michael@0: michael@0: // Grab the end point of the range and QI it to michael@0: // a CharacterData pointer. If it is CharacterData store michael@0: // a pointer to the node. michael@0: michael@0: node = aRange->GetEndContainer(rv); michael@0: if (!node) return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr endData = do_QueryInterface(node); michael@0: if (endData || (node->IsElement() && aRange->GetEndOffset(rv) == 0)) { michael@0: mEnd = node; michael@0: } michael@0: michael@0: if (mStart && mStart == mEnd) michael@0: { michael@0: // The range starts and stops in the same CharacterData michael@0: // node. Null out the end pointer so we only visit the michael@0: // node once! michael@0: michael@0: mEnd = nullptr; michael@0: } michael@0: else michael@0: { michael@0: // Now create a Content Subtree Iterator to be used michael@0: // for the subtrees between the end points! michael@0: michael@0: mIter = NS_NewContentSubtreeIterator(); michael@0: michael@0: nsresult res = mIter->Init(aRange); michael@0: if (NS_FAILED(res)) return res; michael@0: michael@0: if (mIter->IsDone()) michael@0: { michael@0: // The subtree iterator thinks there's nothing michael@0: // to iterate over, so just free it up so we michael@0: // don't accidentally call into it. michael@0: michael@0: mIter = nullptr; michael@0: } michael@0: } michael@0: michael@0: // Initialize the iterator by calling First(). michael@0: // Note that we are ignoring the return value on purpose! michael@0: michael@0: First(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: RangeSubtreeIterator::GetCurrentNode() michael@0: { michael@0: nsCOMPtr node; michael@0: michael@0: if (mIterState == eUseStart && mStart) { michael@0: node = mStart; michael@0: } else if (mIterState == eUseEnd && mEnd) { michael@0: node = mEnd; michael@0: } else if (mIterState == eUseIterator && mIter) { michael@0: node = mIter->GetCurrentNode(); michael@0: } michael@0: michael@0: return node.forget(); michael@0: } michael@0: michael@0: void michael@0: RangeSubtreeIterator::First() michael@0: { michael@0: if (mStart) michael@0: mIterState = eUseStart; michael@0: else if (mIter) michael@0: { michael@0: mIter->First(); michael@0: michael@0: mIterState = eUseIterator; michael@0: } michael@0: else if (mEnd) michael@0: mIterState = eUseEnd; michael@0: else michael@0: mIterState = eDone; michael@0: } michael@0: michael@0: void michael@0: RangeSubtreeIterator::Last() michael@0: { michael@0: if (mEnd) michael@0: mIterState = eUseEnd; michael@0: else if (mIter) michael@0: { michael@0: mIter->Last(); michael@0: michael@0: mIterState = eUseIterator; michael@0: } michael@0: else if (mStart) michael@0: mIterState = eUseStart; michael@0: else michael@0: mIterState = eDone; michael@0: } michael@0: michael@0: void michael@0: RangeSubtreeIterator::Next() michael@0: { michael@0: if (mIterState == eUseStart) michael@0: { michael@0: if (mIter) michael@0: { michael@0: mIter->First(); michael@0: michael@0: mIterState = eUseIterator; michael@0: } michael@0: else if (mEnd) michael@0: mIterState = eUseEnd; michael@0: else michael@0: mIterState = eDone; michael@0: } michael@0: else if (mIterState == eUseIterator) michael@0: { michael@0: mIter->Next(); michael@0: michael@0: if (mIter->IsDone()) michael@0: { michael@0: if (mEnd) michael@0: mIterState = eUseEnd; michael@0: else michael@0: mIterState = eDone; michael@0: } michael@0: } michael@0: else michael@0: mIterState = eDone; michael@0: } michael@0: michael@0: void michael@0: RangeSubtreeIterator::Prev() michael@0: { michael@0: if (mIterState == eUseEnd) michael@0: { michael@0: if (mIter) michael@0: { michael@0: mIter->Last(); michael@0: michael@0: mIterState = eUseIterator; michael@0: } michael@0: else if (mStart) michael@0: mIterState = eUseStart; michael@0: else michael@0: mIterState = eDone; michael@0: } michael@0: else if (mIterState == eUseIterator) michael@0: { michael@0: mIter->Prev(); michael@0: michael@0: if (mIter->IsDone()) michael@0: { michael@0: if (mStart) michael@0: mIterState = eUseStart; michael@0: else michael@0: mIterState = eDone; michael@0: } michael@0: } michael@0: else michael@0: mIterState = eDone; michael@0: } michael@0: michael@0: michael@0: // CollapseRangeAfterDelete() is a utility method that is used by michael@0: // DeleteContents() and ExtractContents() to collapse the range michael@0: // in the correct place, under the range's root container (the michael@0: // range end points common container) as outlined by the Range spec: michael@0: // michael@0: // http://www.w3.org/TR/2000/REC-DOM-Level-2-Traversal-Range-20001113/ranges.html michael@0: // The assumption made by this method is that the delete or extract michael@0: // has been done already, and left the range in a state where there is michael@0: // no content between the 2 end points. michael@0: michael@0: static nsresult michael@0: CollapseRangeAfterDelete(nsRange* aRange) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aRange); michael@0: michael@0: // Check if range gravity took care of collapsing the range for us! michael@0: if (aRange->Collapsed()) michael@0: { michael@0: // aRange is collapsed so there's nothing for us to do. michael@0: // michael@0: // There are 2 possible scenarios here: michael@0: // michael@0: // 1. aRange could've been collapsed prior to the delete/extract, michael@0: // which would've resulted in nothing being removed, so aRange michael@0: // is already where it should be. michael@0: // michael@0: // 2. Prior to the delete/extract, aRange's start and end were in michael@0: // the same container which would mean everything between them michael@0: // was removed, causing range gravity to collapse the range. michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // aRange isn't collapsed so figure out the appropriate place to collapse! michael@0: // First get both end points and their common ancestor. michael@0: michael@0: ErrorResult rv; michael@0: nsCOMPtr commonAncestor = aRange->GetCommonAncestorContainer(rv); michael@0: if (rv.Failed()) return rv.ErrorCode(); michael@0: michael@0: nsCOMPtr startContainer = aRange->GetStartContainer(rv); michael@0: if (rv.Failed()) return rv.ErrorCode(); michael@0: nsCOMPtr endContainer = aRange->GetEndContainer(rv); michael@0: if (rv.Failed()) return rv.ErrorCode(); michael@0: michael@0: // Collapse to one of the end points if they are already in the michael@0: // commonAncestor. This should work ok since this method is called michael@0: // immediately after a delete or extract that leaves no content michael@0: // between the 2 end points! michael@0: michael@0: if (startContainer == commonAncestor) michael@0: return aRange->Collapse(true); michael@0: if (endContainer == commonAncestor) michael@0: return aRange->Collapse(false); michael@0: michael@0: // End points are at differing levels. We want to collapse to the michael@0: // point that is between the 2 subtrees that contain each point, michael@0: // under the common ancestor. michael@0: michael@0: nsCOMPtr nodeToSelect(startContainer); michael@0: michael@0: while (nodeToSelect) michael@0: { michael@0: nsCOMPtr parent = nodeToSelect->GetParentNode(); michael@0: if (parent == commonAncestor) michael@0: break; // We found the nodeToSelect! michael@0: michael@0: nodeToSelect = parent; michael@0: } michael@0: michael@0: if (!nodeToSelect) michael@0: return NS_ERROR_FAILURE; // This should never happen! michael@0: michael@0: aRange->SelectNode(*nodeToSelect, rv); michael@0: if (rv.Failed()) return rv.ErrorCode(); michael@0: michael@0: return aRange->Collapse(false); michael@0: } michael@0: michael@0: /** michael@0: * Split a data node into two parts. michael@0: * michael@0: * @param aStartNode The original node we are trying to split. michael@0: * @param aStartIndex The index at which to split. michael@0: * @param aEndNode The second node. michael@0: * @param aCloneAfterOriginal Set false if the original node should be the michael@0: * latter one after split. michael@0: */ michael@0: static nsresult SplitDataNode(nsIDOMCharacterData* aStartNode, michael@0: uint32_t aStartIndex, michael@0: nsIDOMCharacterData** aEndNode, michael@0: bool aCloneAfterOriginal = true) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr node = do_QueryInterface(aStartNode); michael@0: NS_ENSURE_STATE(node && node->IsNodeOfType(nsINode::eDATA_NODE)); michael@0: nsGenericDOMDataNode* dataNode = static_cast(node.get()); michael@0: michael@0: nsCOMPtr newData; michael@0: rv = dataNode->SplitData(aStartIndex, getter_AddRefs(newData), michael@0: aCloneAfterOriginal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return CallQueryInterface(newData, aEndNode); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PrependChild(nsINode* aParent, nsINode* aChild) michael@0: { michael@0: nsCOMPtr first = aParent->GetFirstChild(); michael@0: ErrorResult rv; michael@0: aParent->InsertBefore(*aChild, first, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: // Helper function for CutContents, making sure that the current node wasn't michael@0: // removed by mutation events (bug 766426) michael@0: static bool michael@0: ValidateCurrentNode(nsRange* aRange, RangeSubtreeIterator& aIter) michael@0: { michael@0: bool before, after; michael@0: nsCOMPtr node = aIter.GetCurrentNode(); michael@0: if (!node) { michael@0: // We don't have to worry that the node was removed if it doesn't exist, michael@0: // e.g., the iterator is done. michael@0: return true; michael@0: } michael@0: michael@0: nsresult res = nsRange::CompareNodeToRange(node, aRange, &before, &after); michael@0: michael@0: return NS_SUCCEEDED(res) && !before && !after; michael@0: } michael@0: michael@0: nsresult michael@0: nsRange::CutContents(DocumentFragment** aFragment) michael@0: { michael@0: if (aFragment) { michael@0: *aFragment = nullptr; michael@0: } michael@0: michael@0: nsCOMPtr doc = mStartParent->OwnerDoc(); michael@0: michael@0: ErrorResult res; michael@0: nsCOMPtr commonAncestor = GetCommonAncestorContainer(res); michael@0: NS_ENSURE_SUCCESS(res.ErrorCode(), res.ErrorCode()); michael@0: michael@0: // If aFragment isn't null, create a temporary fragment to hold our return. michael@0: nsRefPtr retval; michael@0: if (aFragment) { michael@0: retval = new DocumentFragment(doc->NodeInfoManager()); michael@0: } michael@0: nsCOMPtr commonCloneAncestor = retval.get(); michael@0: michael@0: // Batch possible DOMSubtreeModified events. michael@0: mozAutoSubtreeModified subtree(mRoot ? mRoot->OwnerDoc(): nullptr, nullptr); michael@0: michael@0: // Save the range end points locally to avoid interference michael@0: // of Range gravity during our edits! michael@0: michael@0: nsCOMPtr startContainer = mStartParent; michael@0: int32_t startOffset = mStartOffset; michael@0: nsCOMPtr endContainer = mEndParent; michael@0: int32_t endOffset = mEndOffset; michael@0: michael@0: if (retval) { michael@0: // For extractContents(), abort early if there's a doctype (bug 719533). michael@0: // This can happen only if the common ancestor is a document, in which case michael@0: // we just need to find its doctype child and check if that's in the range. michael@0: nsCOMPtr commonAncestorDocument = do_QueryInterface(commonAncestor); michael@0: if (commonAncestorDocument) { michael@0: nsRefPtr doctype = commonAncestorDocument->GetDoctype(); michael@0: michael@0: if (doctype && michael@0: nsContentUtils::ComparePoints(startContainer, startOffset, michael@0: doctype, 0) < 0 && michael@0: nsContentUtils::ComparePoints(doctype, 0, michael@0: endContainer, endOffset) < 0) { michael@0: return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Create and initialize a subtree iterator that will give michael@0: // us all the subtrees within the range. michael@0: michael@0: RangeSubtreeIterator iter; michael@0: michael@0: nsresult rv = iter.Init(this); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (iter.IsDone()) michael@0: { michael@0: // There's nothing for us to delete. michael@0: rv = CollapseRangeAfterDelete(this); michael@0: if (NS_SUCCEEDED(rv) && aFragment) { michael@0: NS_ADDREF(*aFragment = retval); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: // We delete backwards to avoid iterator problems! michael@0: michael@0: iter.Last(); michael@0: michael@0: bool handled = false; michael@0: michael@0: // With the exception of text nodes that contain one of the range michael@0: // end points, the subtree iterator should only give us back subtrees michael@0: // that are completely contained between the range's end points. michael@0: michael@0: while (!iter.IsDone()) michael@0: { michael@0: nsCOMPtr nodeToResult; michael@0: nsCOMPtr node = iter.GetCurrentNode(); michael@0: michael@0: // Before we delete anything, advance the iterator to the michael@0: // next subtree. michael@0: michael@0: iter.Prev(); michael@0: michael@0: handled = false; michael@0: michael@0: // If it's CharacterData, make sure we might need to delete michael@0: // part of the data, instead of removing the whole node. michael@0: // michael@0: // XXX_kin: We need to also handle ProcessingInstruction michael@0: // XXX_kin: according to the spec. michael@0: michael@0: nsCOMPtr charData(do_QueryInterface(node)); michael@0: michael@0: if (charData) michael@0: { michael@0: uint32_t dataLength = 0; michael@0: michael@0: if (node == startContainer) michael@0: { michael@0: if (node == endContainer) michael@0: { michael@0: // This range is completely contained within a single text node. michael@0: // Delete or extract the data between startOffset and endOffset. michael@0: michael@0: if (endOffset > startOffset) michael@0: { michael@0: if (retval) { michael@0: nsAutoString cutValue; michael@0: rv = charData->SubstringData(startOffset, endOffset - startOffset, michael@0: cutValue); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr clone; michael@0: rv = charData->CloneNode(false, 1, getter_AddRefs(clone)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: clone->SetNodeValue(cutValue); michael@0: nodeToResult = do_QueryInterface(clone); michael@0: } michael@0: michael@0: nsMutationGuard guard; michael@0: rv = charData->DeleteData(startOffset, endOffset - startOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_STATE(!guard.Mutated(0) || michael@0: ValidateCurrentNode(this, iter)); michael@0: } michael@0: michael@0: handled = true; michael@0: } michael@0: else michael@0: { michael@0: // Delete or extract everything after startOffset. michael@0: michael@0: rv = charData->GetLength(&dataLength); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (dataLength >= (uint32_t)startOffset) michael@0: { michael@0: nsMutationGuard guard; michael@0: nsCOMPtr cutNode; michael@0: rv = SplitDataNode(charData, startOffset, getter_AddRefs(cutNode)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_STATE(!guard.Mutated(1) || michael@0: ValidateCurrentNode(this, iter)); michael@0: nodeToResult = do_QueryInterface(cutNode); michael@0: } michael@0: michael@0: handled = true; michael@0: } michael@0: } michael@0: else if (node == endContainer) michael@0: { michael@0: // Delete or extract everything before endOffset. michael@0: michael@0: if (endOffset >= 0) michael@0: { michael@0: nsMutationGuard guard; michael@0: nsCOMPtr cutNode; michael@0: /* The Range spec clearly states clones get cut and original nodes michael@0: remain behind, so use false as the last parameter. michael@0: */ michael@0: rv = SplitDataNode(charData, endOffset, getter_AddRefs(cutNode), michael@0: false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_STATE(!guard.Mutated(1) || michael@0: ValidateCurrentNode(this, iter)); michael@0: nodeToResult = do_QueryInterface(cutNode); michael@0: } michael@0: michael@0: handled = true; michael@0: } michael@0: } michael@0: michael@0: if (!handled && (node == endContainer || node == startContainer)) michael@0: { michael@0: if (node && node->IsElement() && michael@0: ((node == endContainer && endOffset == 0) || michael@0: (node == startContainer && michael@0: int32_t(node->AsElement()->GetChildCount()) == startOffset))) michael@0: { michael@0: if (retval) { michael@0: ErrorResult rv; michael@0: nodeToResult = node->CloneNode(false, rv); michael@0: NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode()); michael@0: } michael@0: handled = true; michael@0: } michael@0: } michael@0: michael@0: if (!handled) michael@0: { michael@0: // node was not handled above, so it must be completely contained michael@0: // within the range. Just remove it from the tree! michael@0: nodeToResult = node; michael@0: } michael@0: michael@0: uint32_t parentCount = 0; michael@0: // Set the result to document fragment if we have 'retval'. michael@0: if (retval) { michael@0: nsCOMPtr oldCommonAncestor = commonAncestor; michael@0: if (!iter.IsDone()) { michael@0: // Setup the parameters for the next iteration of the loop. michael@0: nsCOMPtr prevNode = iter.GetCurrentNode(); michael@0: NS_ENSURE_STATE(prevNode); michael@0: michael@0: // Get node's and prevNode's common parent. Do this before moving michael@0: // nodes from original DOM to result fragment. michael@0: commonAncestor = nsContentUtils::GetCommonAncestor(node, prevNode); michael@0: NS_ENSURE_STATE(commonAncestor); michael@0: michael@0: nsCOMPtr parentCounterNode = node; michael@0: while (parentCounterNode && parentCounterNode != commonAncestor) michael@0: { michael@0: ++parentCount; michael@0: parentCounterNode = parentCounterNode->GetParentNode(); michael@0: NS_ENSURE_STATE(parentCounterNode); michael@0: } michael@0: } michael@0: michael@0: // Clone the parent hierarchy between commonAncestor and node. michael@0: nsCOMPtr closestAncestor, farthestAncestor; michael@0: rv = CloneParentsBetween(oldCommonAncestor, node, michael@0: getter_AddRefs(closestAncestor), michael@0: getter_AddRefs(farthestAncestor)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (farthestAncestor) michael@0: { michael@0: nsCOMPtr n = do_QueryInterface(commonCloneAncestor); michael@0: rv = PrependChild(n, farthestAncestor); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsMutationGuard guard; michael@0: nsCOMPtr parent = nodeToResult->GetParentNode(); michael@0: rv = closestAncestor ? PrependChild(closestAncestor, nodeToResult) michael@0: : PrependChild(commonCloneAncestor, nodeToResult); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_STATE(!guard.Mutated(parent ? 2 : 1) || michael@0: ValidateCurrentNode(this, iter)); michael@0: } else if (nodeToResult) { michael@0: nsMutationGuard guard; michael@0: nsCOMPtr node = nodeToResult; michael@0: nsINode* parent = node->GetParentNode(); michael@0: if (parent) { michael@0: mozilla::ErrorResult error; michael@0: parent->RemoveChild(*node, error); michael@0: NS_ENSURE_FALSE(error.Failed(), error.ErrorCode()); michael@0: } michael@0: NS_ENSURE_STATE(!guard.Mutated(1) || michael@0: ValidateCurrentNode(this, iter)); michael@0: } michael@0: michael@0: if (!iter.IsDone() && retval) { michael@0: // Find the equivalent of commonAncestor in the cloned tree. michael@0: nsCOMPtr newCloneAncestor = nodeToResult; michael@0: for (uint32_t i = parentCount; i; --i) michael@0: { michael@0: newCloneAncestor = newCloneAncestor->GetParentNode(); michael@0: NS_ENSURE_STATE(newCloneAncestor); michael@0: } michael@0: commonCloneAncestor = newCloneAncestor; michael@0: } michael@0: } michael@0: michael@0: rv = CollapseRangeAfterDelete(this); michael@0: if (NS_SUCCEEDED(rv) && aFragment) { michael@0: NS_ADDREF(*aFragment = retval); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::DeleteContents() michael@0: { michael@0: return CutContents(nullptr); michael@0: } michael@0: michael@0: void michael@0: nsRange::DeleteContents(ErrorResult& aRv) michael@0: { michael@0: aRv = CutContents(nullptr); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::ExtractContents(nsIDOMDocumentFragment** aReturn) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aReturn); michael@0: nsRefPtr fragment; michael@0: nsresult rv = CutContents(getter_AddRefs(fragment)); michael@0: fragment.forget(aReturn); michael@0: return rv; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsRange::ExtractContents(ErrorResult& rv) michael@0: { michael@0: nsRefPtr fragment; michael@0: rv = CutContents(getter_AddRefs(fragment)); michael@0: return fragment.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::CompareBoundaryPoints(uint16_t aHow, nsIDOMRange* aOtherRange, michael@0: int16_t* aCmpRet) michael@0: { michael@0: nsRange* otherRange = static_cast(aOtherRange); michael@0: NS_ENSURE_TRUE(otherRange, NS_ERROR_NULL_POINTER); michael@0: michael@0: ErrorResult rv; michael@0: *aCmpRet = CompareBoundaryPoints(aHow, *otherRange, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: int16_t michael@0: nsRange::CompareBoundaryPoints(uint16_t aHow, nsRange& aOtherRange, michael@0: ErrorResult& rv) michael@0: { michael@0: if (!mIsPositioned || !aOtherRange.IsPositioned()) { michael@0: rv.Throw(NS_ERROR_NOT_INITIALIZED); michael@0: return 0; michael@0: } michael@0: michael@0: nsINode *ourNode, *otherNode; michael@0: int32_t ourOffset, otherOffset; michael@0: michael@0: switch (aHow) { michael@0: case nsIDOMRange::START_TO_START: michael@0: ourNode = mStartParent; michael@0: ourOffset = mStartOffset; michael@0: otherNode = aOtherRange.GetStartParent(); michael@0: otherOffset = aOtherRange.StartOffset(); michael@0: break; michael@0: case nsIDOMRange::START_TO_END: michael@0: ourNode = mEndParent; michael@0: ourOffset = mEndOffset; michael@0: otherNode = aOtherRange.GetStartParent(); michael@0: otherOffset = aOtherRange.StartOffset(); michael@0: break; michael@0: case nsIDOMRange::END_TO_START: michael@0: ourNode = mStartParent; michael@0: ourOffset = mStartOffset; michael@0: otherNode = aOtherRange.GetEndParent(); michael@0: otherOffset = aOtherRange.EndOffset(); michael@0: break; michael@0: case nsIDOMRange::END_TO_END: michael@0: ourNode = mEndParent; michael@0: ourOffset = mEndOffset; michael@0: otherNode = aOtherRange.GetEndParent(); michael@0: otherOffset = aOtherRange.EndOffset(); michael@0: break; michael@0: default: michael@0: // We were passed an illegal value michael@0: rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); michael@0: return 0; michael@0: } michael@0: michael@0: if (mRoot != aOtherRange.GetRoot()) { michael@0: rv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR); michael@0: return 0; michael@0: } michael@0: michael@0: return nsContentUtils::ComparePoints(ourNode, ourOffset, michael@0: otherNode, otherOffset); michael@0: } michael@0: michael@0: /* static */ nsresult michael@0: nsRange::CloneParentsBetween(nsINode *aAncestor, michael@0: nsINode *aNode, michael@0: nsINode **aClosestAncestor, michael@0: nsINode **aFarthestAncestor) michael@0: { michael@0: NS_ENSURE_ARG_POINTER((aAncestor && aNode && aClosestAncestor && aFarthestAncestor)); michael@0: michael@0: *aClosestAncestor = nullptr; michael@0: *aFarthestAncestor = nullptr; michael@0: michael@0: if (aAncestor == aNode) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr firstParent, lastParent; michael@0: nsCOMPtr parent = aNode->GetParentNode(); michael@0: michael@0: while(parent && parent != aAncestor) michael@0: { michael@0: ErrorResult rv; michael@0: nsCOMPtr clone = parent->CloneNode(false, rv); michael@0: michael@0: if (rv.Failed()) { michael@0: return rv.ErrorCode(); michael@0: } michael@0: if (!clone) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (! firstParent) { michael@0: firstParent = lastParent = clone; michael@0: } else { michael@0: clone->AppendChild(*lastParent, rv); michael@0: if (rv.Failed()) return rv.ErrorCode(); michael@0: michael@0: lastParent = clone; michael@0: } michael@0: michael@0: parent = parent->GetParentNode(); michael@0: } michael@0: michael@0: *aClosestAncestor = firstParent; michael@0: NS_IF_ADDREF(*aClosestAncestor); michael@0: michael@0: *aFarthestAncestor = lastParent; michael@0: NS_IF_ADDREF(*aFarthestAncestor); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::CloneContents(nsIDOMDocumentFragment** aReturn) michael@0: { michael@0: ErrorResult rv; michael@0: *aReturn = CloneContents(rv).take(); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsRange::CloneContents(ErrorResult& aRv) michael@0: { michael@0: nsCOMPtr commonAncestor = GetCommonAncestorContainer(aRv); michael@0: MOZ_ASSERT(!aRv.Failed(), "GetCommonAncestorContainer() shouldn't fail!"); michael@0: michael@0: nsCOMPtr doc = mStartParent->OwnerDoc(); michael@0: NS_ASSERTION(doc, "CloneContents needs a document to continue."); michael@0: if (!doc) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Create a new document fragment in the context of this document, michael@0: // which might be null michael@0: michael@0: michael@0: nsRefPtr clonedFrag = michael@0: new DocumentFragment(doc->NodeInfoManager()); michael@0: michael@0: nsCOMPtr commonCloneAncestor = clonedFrag.get(); michael@0: michael@0: // Create and initialize a subtree iterator that will give michael@0: // us all the subtrees within the range. michael@0: michael@0: RangeSubtreeIterator iter; michael@0: michael@0: aRv = iter.Init(this); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (iter.IsDone()) michael@0: { michael@0: // There's nothing to add to the doc frag, we must be done! michael@0: return clonedFrag.forget(); michael@0: } michael@0: michael@0: iter.First(); michael@0: michael@0: // With the exception of text nodes that contain one of the range michael@0: // end points and elements which don't have any content selected the subtree michael@0: // iterator should only give us back subtrees that are completely contained michael@0: // between the range's end points. michael@0: // michael@0: // Unfortunately these subtrees don't contain the parent hierarchy/context michael@0: // that the Range spec requires us to return. This loop clones the michael@0: // parent hierarchy, adds a cloned version of the subtree, to it, then michael@0: // correctly places this new subtree into the doc fragment. michael@0: michael@0: while (!iter.IsDone()) michael@0: { michael@0: nsCOMPtr node = iter.GetCurrentNode(); michael@0: bool deepClone = !node->IsElement() || michael@0: (!(node == mEndParent && mEndOffset == 0) && michael@0: !(node == mStartParent && michael@0: mStartOffset == michael@0: int32_t(node->AsElement()->GetChildCount()))); michael@0: michael@0: // Clone the current subtree! michael@0: michael@0: nsCOMPtr clone = node->CloneNode(deepClone, aRv); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // If it's CharacterData, make sure we only clone what michael@0: // is in the range. michael@0: // michael@0: // XXX_kin: We need to also handle ProcessingInstruction michael@0: // XXX_kin: according to the spec. michael@0: michael@0: nsCOMPtr charData(do_QueryInterface(clone)); michael@0: michael@0: if (charData) michael@0: { michael@0: if (node == mEndParent) michael@0: { michael@0: // We only need the data before mEndOffset, so get rid of any michael@0: // data after it. michael@0: michael@0: uint32_t dataLength = 0; michael@0: aRv = charData->GetLength(&dataLength); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (dataLength > (uint32_t)mEndOffset) michael@0: { michael@0: aRv = charData->DeleteData(mEndOffset, dataLength - mEndOffset); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (node == mStartParent) michael@0: { michael@0: // We don't need any data before mStartOffset, so just michael@0: // delete it! michael@0: michael@0: if (mStartOffset > 0) michael@0: { michael@0: aRv = charData->DeleteData(0, mStartOffset); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Clone the parent hierarchy between commonAncestor and node. michael@0: michael@0: nsCOMPtr closestAncestor, farthestAncestor; michael@0: michael@0: aRv = CloneParentsBetween(commonAncestor, node, michael@0: getter_AddRefs(closestAncestor), michael@0: getter_AddRefs(farthestAncestor)); michael@0: michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Hook the parent hierarchy/context of the subtree into the clone tree. michael@0: michael@0: if (farthestAncestor) michael@0: { michael@0: commonCloneAncestor->AppendChild(*farthestAncestor, aRv); michael@0: michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: // Place the cloned subtree into the cloned doc frag tree! michael@0: michael@0: nsCOMPtr cloneNode = do_QueryInterface(clone); michael@0: if (closestAncestor) michael@0: { michael@0: // Append the subtree under closestAncestor since it is the michael@0: // immediate parent of the subtree. michael@0: michael@0: closestAncestor->AppendChild(*cloneNode, aRv); michael@0: } michael@0: else michael@0: { michael@0: // If we get here, there is no missing parent hierarchy between michael@0: // commonAncestor and node, so just append clone to commonCloneAncestor. michael@0: michael@0: commonCloneAncestor->AppendChild(*cloneNode, aRv); michael@0: } michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Get the next subtree to be processed. The idea here is to setup michael@0: // the parameters for the next iteration of the loop. michael@0: michael@0: iter.Next(); michael@0: michael@0: if (iter.IsDone()) michael@0: break; // We must be done! michael@0: michael@0: nsCOMPtr nextNode = iter.GetCurrentNode(); michael@0: if (!nextNode) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Get node and nextNode's common parent. michael@0: commonAncestor = nsContentUtils::GetCommonAncestor(node, nextNode); michael@0: michael@0: if (!commonAncestor) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Find the equivalent of commonAncestor in the cloned tree! michael@0: michael@0: while (node && node != commonAncestor) michael@0: { michael@0: node = node->GetParentNode(); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!node) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: cloneNode = cloneNode->GetParentNode(); michael@0: if (!cloneNode) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: commonCloneAncestor = cloneNode; michael@0: } michael@0: michael@0: return clonedFrag.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsRange::CloneRange() const michael@0: { michael@0: nsRefPtr range = new nsRange(mOwner); michael@0: michael@0: range->SetMaySpanAnonymousSubtrees(mMaySpanAnonymousSubtrees); michael@0: michael@0: range->DoSetRange(mStartParent, mStartOffset, mEndParent, mEndOffset, mRoot); michael@0: michael@0: return range.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::CloneRange(nsIDOMRange** aReturn) michael@0: { michael@0: *aReturn = CloneRange().take(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::InsertNode(nsIDOMNode* aNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: if (!node) { michael@0: return NS_ERROR_DOM_NOT_OBJECT_ERR; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: InsertNode(*node, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsRange::InsertNode(nsINode& aNode, ErrorResult& aRv) michael@0: { michael@0: if (!nsContentUtils::CanCallerAccess(&aNode)) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: int32_t tStartOffset = StartOffset(); michael@0: michael@0: nsCOMPtr tStartContainer = GetStartContainer(aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: // This is the node we'll be inserting before, and its parent michael@0: nsCOMPtr referenceNode; michael@0: nsCOMPtr referenceParentNode = tStartContainer; michael@0: michael@0: nsCOMPtr startTextNode(do_QueryInterface(tStartContainer)); michael@0: nsCOMPtr tChildList; michael@0: if (startTextNode) { michael@0: referenceParentNode = tStartContainer->GetParentNode(); michael@0: if (!referenceParentNode) { michael@0: aRv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr secondPart; michael@0: aRv = startTextNode->SplitText(tStartOffset, getter_AddRefs(secondPart)); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: referenceNode = do_QueryInterface(secondPart); michael@0: } else { michael@0: aRv = tStartContainer->AsDOMNode()->GetChildNodes(getter_AddRefs(tChildList)); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: // find the insertion point in the DOM and insert the Node michael@0: nsCOMPtr q; michael@0: aRv = tChildList->Item(tStartOffset, getter_AddRefs(q)); michael@0: referenceNode = do_QueryInterface(q); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // We might need to update the end to include the new node (bug 433662). michael@0: // Ideally we'd only do this if needed, but it's tricky to know when it's michael@0: // needed in advance (bug 765799). michael@0: int32_t newOffset; michael@0: michael@0: if (referenceNode) { michael@0: newOffset = IndexOf(referenceNode); michael@0: } else { michael@0: uint32_t length; michael@0: aRv = tChildList->GetLength(&length); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: newOffset = length; michael@0: } michael@0: michael@0: if (aNode.NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) { michael@0: newOffset += aNode.GetChildCount(); michael@0: } else { michael@0: newOffset++; michael@0: } michael@0: michael@0: // Now actually insert the node michael@0: nsCOMPtr tResultNode; michael@0: tResultNode = referenceParentNode->InsertBefore(aNode, referenceNode, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: if (Collapsed()) { michael@0: aRv = SetEnd(referenceParentNode, newOffset); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::SurroundContents(nsIDOMNode* aNewParent) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNewParent); michael@0: if (!node) { michael@0: return NS_ERROR_DOM_NOT_OBJECT_ERR; michael@0: } michael@0: ErrorResult rv; michael@0: SurroundContents(*node, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsRange::SurroundContents(nsINode& aNewParent, ErrorResult& aRv) michael@0: { michael@0: if (!nsContentUtils::CanCallerAccess(&aNewParent)) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (!mRoot) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: // INVALID_STATE_ERROR: Raised if the Range partially selects a non-text michael@0: // node. michael@0: if (mStartParent != mEndParent) { michael@0: bool startIsText = mStartParent->IsNodeOfType(nsINode::eTEXT); michael@0: bool endIsText = mEndParent->IsNodeOfType(nsINode::eTEXT); michael@0: nsINode* startGrandParent = mStartParent->GetParentNode(); michael@0: nsINode* endGrandParent = mEndParent->GetParentNode(); michael@0: if (!((startIsText && endIsText && michael@0: startGrandParent && michael@0: startGrandParent == endGrandParent) || michael@0: (startIsText && michael@0: startGrandParent && michael@0: startGrandParent == mEndParent) || michael@0: (endIsText && michael@0: endGrandParent && michael@0: endGrandParent == mStartParent))) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // INVALID_NODE_TYPE_ERROR if aNewParent is something that can't be inserted michael@0: // (Document, DocumentType, DocumentFragment) michael@0: uint16_t nodeType = aNewParent.NodeType(); michael@0: if (nodeType == nsIDOMNode::DOCUMENT_NODE || michael@0: nodeType == nsIDOMNode::DOCUMENT_TYPE_NODE || michael@0: nodeType == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); michael@0: return; michael@0: } michael@0: michael@0: // Extract the contents within the range. michael@0: michael@0: nsRefPtr docFrag = ExtractContents(aRv); michael@0: michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: if (!docFrag) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: // Spec says we need to remove all of aNewParent's michael@0: // children prior to insertion. michael@0: michael@0: nsCOMPtr children = aNewParent.ChildNodes(); michael@0: if (!children) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: uint32_t numChildren = children->Length(); michael@0: michael@0: while (numChildren) michael@0: { michael@0: nsCOMPtr child = children->Item(--numChildren); michael@0: if (!child) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: aNewParent.RemoveChild(*child, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Insert aNewParent at the range's start point. michael@0: michael@0: InsertNode(aNewParent, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: // Append the content we extracted under aNewParent. michael@0: aNewParent.AppendChild(*docFrag, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: // Select aNewParent, and its contents. michael@0: michael@0: SelectNode(aNewParent, aRv); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::ToString(nsAString& aReturn) michael@0: { michael@0: // clear the string michael@0: aReturn.Truncate(); michael@0: michael@0: // If we're unpositioned, return the empty string michael@0: if (!mIsPositioned) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef DEBUG_range michael@0: printf("Range dump: -----------------------\n"); michael@0: #endif /* DEBUG */ michael@0: michael@0: // effeciency hack for simple case michael@0: if (mStartParent == mEndParent) michael@0: { michael@0: nsCOMPtr textNode( do_QueryInterface(mStartParent) ); michael@0: michael@0: if (textNode) michael@0: { michael@0: #ifdef DEBUG_range michael@0: // If debug, dump it: michael@0: nsCOMPtr cN (do_QueryInterface(mStartParent)); michael@0: if (cN) cN->List(stdout); michael@0: printf("End Range dump: -----------------------\n"); michael@0: #endif /* DEBUG */ michael@0: michael@0: // grab the text michael@0: if (NS_FAILED(textNode->SubstringData(mStartOffset,mEndOffset-mStartOffset,aReturn))) michael@0: return NS_ERROR_UNEXPECTED; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: /* complex case: mStartParent != mEndParent, or mStartParent not a text node michael@0: revisit - there are potential optimizations here and also tradeoffs. michael@0: */ michael@0: michael@0: nsCOMPtr iter = NS_NewContentIterator(); michael@0: nsresult rv = iter->Init(this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsString tempString; michael@0: michael@0: // loop through the content iterator, which returns nodes in the range in michael@0: // close tag order, and grab the text from any text node michael@0: while (!iter->IsDone()) michael@0: { michael@0: nsINode *n = iter->GetCurrentNode(); michael@0: michael@0: #ifdef DEBUG_range michael@0: // If debug, dump it: michael@0: n->List(stdout); michael@0: #endif /* DEBUG */ michael@0: nsCOMPtr textNode(do_QueryInterface(n)); michael@0: if (textNode) // if it's a text node, get the text michael@0: { michael@0: if (n == mStartParent) // only include text past start offset michael@0: { michael@0: uint32_t strLength; michael@0: textNode->GetLength(&strLength); michael@0: textNode->SubstringData(mStartOffset,strLength-mStartOffset,tempString); michael@0: aReturn += tempString; michael@0: } michael@0: else if (n == mEndParent) // only include text before end offset michael@0: { michael@0: textNode->SubstringData(0,mEndOffset,tempString); michael@0: aReturn += tempString; michael@0: } michael@0: else // grab the whole kit-n-kaboodle michael@0: { michael@0: textNode->GetData(tempString); michael@0: aReturn += tempString; michael@0: } michael@0: } michael@0: michael@0: iter->Next(); michael@0: } michael@0: michael@0: #ifdef DEBUG_range michael@0: printf("End Range dump: -----------------------\n"); michael@0: #endif /* DEBUG */ michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::Detach() michael@0: { michael@0: // No-op, but still set mIsDetached for telemetry (bug 702948) michael@0: mIsDetached = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::CreateContextualFragment(const nsAString& aFragment, michael@0: nsIDOMDocumentFragment** aReturn) michael@0: { michael@0: if (mIsPositioned) { michael@0: return nsContentUtils::CreateContextualFragment(mStartParent, aFragment, michael@0: false, aReturn); michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsRange::CreateContextualFragment(const nsAString& aFragment, ErrorResult& aRv) michael@0: { michael@0: if (!mIsPositioned) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: return nsContentUtils::CreateContextualFragment(mStartParent, aFragment, michael@0: false, aRv); michael@0: } michael@0: michael@0: static void ExtractRectFromOffset(nsIFrame* aFrame, michael@0: const nsIFrame* aRelativeTo, michael@0: const int32_t aOffset, nsRect* aR, bool aKeepLeft) michael@0: { michael@0: nsPoint point; michael@0: aFrame->GetPointFromOffset(aOffset, &point); michael@0: michael@0: point += aFrame->GetOffsetTo(aRelativeTo); michael@0: michael@0: //given a point.x, extract left or right portion of rect aR michael@0: //point.x has to be within this rect michael@0: NS_ASSERTION(aR->x <= point.x && point.x <= aR->XMost(), michael@0: "point.x should not be outside of rect r"); michael@0: michael@0: if (aKeepLeft) { michael@0: aR->width = point.x - aR->x; michael@0: } else { michael@0: aR->width = aR->XMost() - point.x; michael@0: aR->x = point.x; michael@0: } michael@0: } michael@0: michael@0: static nsTextFrame* michael@0: GetTextFrameForContent(nsIContent* aContent) michael@0: { michael@0: nsIPresShell* presShell = aContent->OwnerDoc()->GetShell(); michael@0: if (presShell) { michael@0: presShell->FrameConstructor()->EnsureFrameForTextNode( michael@0: static_cast(aContent)); michael@0: aContent->OwnerDoc()->FlushPendingNotifications(Flush_Layout); michael@0: nsIFrame* frame = aContent->GetPrimaryFrame(); michael@0: if (frame && frame->GetType() == nsGkAtoms::textFrame) { michael@0: return static_cast(frame); michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: static nsresult GetPartialTextRect(nsLayoutUtils::RectCallback* aCallback, michael@0: nsIContent* aContent, int32_t aStartOffset, int32_t aEndOffset) michael@0: { michael@0: nsTextFrame* textFrame = GetTextFrameForContent(aContent); michael@0: if (textFrame) { michael@0: nsIFrame* relativeTo = nsLayoutUtils::GetContainingBlockForClientRect(textFrame); michael@0: for (nsTextFrame* f = textFrame; f; f = static_cast(f->GetNextContinuation())) { michael@0: int32_t fstart = f->GetContentOffset(), fend = f->GetContentEnd(); michael@0: if (fend <= aStartOffset || fstart >= aEndOffset) michael@0: continue; michael@0: michael@0: // overlapping with the offset we want michael@0: f->EnsureTextRun(nsTextFrame::eInflated); michael@0: NS_ENSURE_TRUE(f->GetTextRun(nsTextFrame::eInflated), NS_ERROR_OUT_OF_MEMORY); michael@0: bool rtl = f->GetTextRun(nsTextFrame::eInflated)->IsRightToLeft(); michael@0: nsRect r(f->GetOffsetTo(relativeTo), f->GetSize()); michael@0: if (fstart < aStartOffset) { michael@0: // aStartOffset is within this frame michael@0: ExtractRectFromOffset(f, relativeTo, aStartOffset, &r, rtl); michael@0: } michael@0: if (fend > aEndOffset) { michael@0: // aEndOffset is in the middle of this frame michael@0: ExtractRectFromOffset(f, relativeTo, aEndOffset, &r, !rtl); michael@0: } michael@0: aCallback->AddRect(r); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void CollectClientRects(nsLayoutUtils::RectCallback* aCollector, michael@0: nsRange* aRange, michael@0: nsINode* aStartParent, int32_t aStartOffset, michael@0: nsINode* aEndParent, int32_t aEndOffset) michael@0: { michael@0: // Hold strong pointers across the flush michael@0: nsCOMPtr startContainer = aStartParent; michael@0: nsCOMPtr endContainer = aEndParent; michael@0: michael@0: // Flush out layout so our frames are up to date. michael@0: if (!aStartParent->IsInDoc()) { michael@0: return; michael@0: } michael@0: michael@0: aStartParent->OwnerDoc()->FlushPendingNotifications(Flush_Layout); michael@0: michael@0: // Recheck whether we're still in the document michael@0: if (!aStartParent->IsInDoc()) { michael@0: return; michael@0: } michael@0: michael@0: RangeSubtreeIterator iter; michael@0: michael@0: nsresult rv = iter.Init(aRange); michael@0: if (NS_FAILED(rv)) return; michael@0: michael@0: if (iter.IsDone()) { michael@0: // the range is collapsed, only continue if the cursor is in a text node michael@0: nsCOMPtr content = do_QueryInterface(aStartParent); michael@0: if (content && content->IsNodeOfType(nsINode::eTEXT)) { michael@0: nsTextFrame* textFrame = GetTextFrameForContent(content); michael@0: if (textFrame) { michael@0: int32_t outOffset; michael@0: nsIFrame* outFrame; michael@0: textFrame->GetChildFrameContainingOffset(aStartOffset, false, michael@0: &outOffset, &outFrame); michael@0: if (outFrame) { michael@0: nsIFrame* relativeTo = michael@0: nsLayoutUtils::GetContainingBlockForClientRect(outFrame); michael@0: nsRect r(outFrame->GetOffsetTo(relativeTo), outFrame->GetSize()); michael@0: ExtractRectFromOffset(outFrame, relativeTo, aStartOffset, &r, false); michael@0: r.width = 0; michael@0: aCollector->AddRect(r); michael@0: } michael@0: } michael@0: } michael@0: return; michael@0: } michael@0: michael@0: do { michael@0: nsCOMPtr node = iter.GetCurrentNode(); michael@0: iter.Next(); michael@0: nsCOMPtr content = do_QueryInterface(node); michael@0: if (!content) michael@0: continue; michael@0: if (content->IsNodeOfType(nsINode::eTEXT)) { michael@0: if (node == startContainer) { michael@0: int32_t offset = startContainer == endContainer ? michael@0: aEndOffset : content->GetText()->GetLength(); michael@0: GetPartialTextRect(aCollector, content, aStartOffset, offset); michael@0: continue; michael@0: } else if (node == endContainer) { michael@0: GetPartialTextRect(aCollector, content, 0, aEndOffset); michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: nsIFrame* frame = content->GetPrimaryFrame(); michael@0: if (frame) { michael@0: nsLayoutUtils::GetAllInFlowRects(frame, michael@0: nsLayoutUtils::GetContainingBlockForClientRect(frame), aCollector); michael@0: } michael@0: } while (!iter.IsDone()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::GetBoundingClientRect(nsIDOMClientRect** aResult) michael@0: { michael@0: *aResult = GetBoundingClientRect().take(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsRange::GetBoundingClientRect() michael@0: { michael@0: nsRefPtr rect = new DOMRect(ToSupports(this)); michael@0: if (!mStartParent) { michael@0: return rect.forget(); michael@0: } michael@0: michael@0: nsLayoutUtils::RectAccumulator accumulator; michael@0: CollectClientRects(&accumulator, this, mStartParent, mStartOffset, michael@0: mEndParent, mEndOffset); michael@0: michael@0: nsRect r = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect : michael@0: accumulator.mResultRect; michael@0: rect->SetLayoutRect(r); michael@0: return rect.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::GetClientRects(nsIDOMClientRectList** aResult) michael@0: { michael@0: *aResult = GetClientRects().take(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsRange::GetClientRects() michael@0: { michael@0: if (!mStartParent) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr rectList = michael@0: new DOMRectList(static_cast(this)); michael@0: michael@0: nsLayoutUtils::RectListBuilder builder(rectList); michael@0: michael@0: CollectClientRects(&builder, this, mStartParent, mStartOffset, michael@0: mEndParent, mEndOffset); michael@0: return rectList.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsRange::GetUsedFontFaces(nsIDOMFontFaceList** aResult) michael@0: { michael@0: *aResult = nullptr; michael@0: michael@0: NS_ENSURE_TRUE(mStartParent, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsCOMPtr startContainer = do_QueryInterface(mStartParent); michael@0: nsCOMPtr endContainer = do_QueryInterface(mEndParent); michael@0: michael@0: // Flush out layout so our frames are up to date. michael@0: nsIDocument* doc = mStartParent->OwnerDoc(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); michael@0: doc->FlushPendingNotifications(Flush_Frames); michael@0: michael@0: // Recheck whether we're still in the document michael@0: NS_ENSURE_TRUE(mStartParent->IsInDoc(), NS_ERROR_UNEXPECTED); michael@0: michael@0: nsRefPtr fontFaceList = new nsFontFaceList(); michael@0: michael@0: RangeSubtreeIterator iter; michael@0: nsresult rv = iter.Init(this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: while (!iter.IsDone()) { michael@0: // only collect anything if the range is not collapsed michael@0: nsCOMPtr node = iter.GetCurrentNode(); michael@0: iter.Next(); michael@0: michael@0: nsCOMPtr content = do_QueryInterface(node); michael@0: if (!content) { michael@0: continue; michael@0: } michael@0: nsIFrame* frame = content->GetPrimaryFrame(); michael@0: if (!frame) { michael@0: continue; michael@0: } michael@0: michael@0: if (content->IsNodeOfType(nsINode::eTEXT)) { michael@0: if (node == startContainer) { michael@0: int32_t offset = startContainer == endContainer ? michael@0: mEndOffset : content->GetText()->GetLength(); michael@0: nsLayoutUtils::GetFontFacesForText(frame, mStartOffset, offset, michael@0: true, fontFaceList); michael@0: continue; michael@0: } michael@0: if (node == endContainer) { michael@0: nsLayoutUtils::GetFontFacesForText(frame, 0, mEndOffset, michael@0: true, fontFaceList); michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: nsLayoutUtils::GetFontFacesForFrames(frame, fontFaceList); michael@0: } michael@0: michael@0: fontFaceList.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsINode* michael@0: nsRange::GetRegisteredCommonAncestor() michael@0: { michael@0: NS_ASSERTION(IsInSelection(), michael@0: "GetRegisteredCommonAncestor only valid for range in selection"); michael@0: nsINode* ancestor = GetNextRangeCommonAncestor(mStartParent); michael@0: while (ancestor) { michael@0: RangeHashTable* ranges = michael@0: static_cast(ancestor->GetProperty(nsGkAtoms::range)); michael@0: if (ranges->GetEntry(this)) { michael@0: break; michael@0: } michael@0: ancestor = GetNextRangeCommonAncestor(ancestor->GetParentNode()); michael@0: } michael@0: NS_ASSERTION(ancestor, "can't find common ancestor for selected range"); michael@0: return ancestor; michael@0: } michael@0: michael@0: /* static */ bool nsRange::AutoInvalidateSelection::mIsNested; michael@0: michael@0: nsRange::AutoInvalidateSelection::~AutoInvalidateSelection() michael@0: { michael@0: NS_ASSERTION(mWasInSelection == mRange->IsInSelection(), michael@0: "Range got unselected in AutoInvalidateSelection block"); michael@0: if (!mCommonAncestor) { michael@0: return; michael@0: } michael@0: mIsNested = false; michael@0: ::InvalidateAllFrames(mCommonAncestor); michael@0: nsINode* commonAncestor = mRange->GetRegisteredCommonAncestor(); michael@0: if (commonAncestor != mCommonAncestor) { michael@0: ::InvalidateAllFrames(commonAncestor); michael@0: } michael@0: } michael@0: michael@0: /* static */ already_AddRefed michael@0: nsRange::Constructor(const GlobalObject& aGlobal, michael@0: ErrorResult& aRv) michael@0: { michael@0: nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); michael@0: if (!window || !window->GetDoc()) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: return window->GetDoc()->CreateRange(aRv); michael@0: }