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: * Object that can be used to serialize selections, ranges, or nodes michael@0: * to strings in a gazillion different ways. michael@0: */ michael@0: michael@0: #include "nsIDocumentEncoder.h" michael@0: michael@0: #include "nscore.h" michael@0: #include "nsIFactory.h" michael@0: #include "nsISupports.h" michael@0: #include "nsIComponentManager.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIHTMLDocument.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIContentSerializer.h" michael@0: #include "nsIUnicodeEncoder.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMText.h" michael@0: #include "nsIDOMCDATASection.h" michael@0: #include "nsIDOMComment.h" michael@0: #include "nsIDOMProcessingInstruction.h" michael@0: #include "nsIDOMDocumentType.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsRange.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsICharsetConverterManager.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIParserService.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include "nsISelectionPrivate.h" michael@0: #include "nsITransferable.h" // for kUnicodeMime michael@0: #include "nsContentUtils.h" michael@0: #include "nsNodeUtils.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsTArray.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsStringBuffer.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/ShadowRoot.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIHTMLEditor.h" michael@0: #include "nsIDocShell.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: nsresult NS_NewDomSelection(nsISelection **aDomSelection); michael@0: michael@0: enum nsRangeIterationDirection { michael@0: kDirectionOut = -1, michael@0: kDirectionIn = 1 michael@0: }; michael@0: michael@0: class nsDocumentEncoder : public nsIDocumentEncoder michael@0: { michael@0: public: michael@0: nsDocumentEncoder(); michael@0: virtual ~nsDocumentEncoder(); michael@0: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_CLASS(nsDocumentEncoder) michael@0: NS_DECL_NSIDOCUMENTENCODER michael@0: michael@0: protected: michael@0: void Initialize(bool aClearCachedSerializer = true); michael@0: nsresult SerializeNodeStart(nsINode* aNode, int32_t aStartOffset, michael@0: int32_t aEndOffset, nsAString& aStr, michael@0: nsINode* aOriginalNode = nullptr); michael@0: nsresult SerializeToStringRecursive(nsINode* aNode, michael@0: nsAString& aStr, michael@0: bool aDontSerializeRoot, michael@0: uint32_t aMaxLength = 0); michael@0: nsresult SerializeNodeEnd(nsINode* aNode, nsAString& aStr); michael@0: // This serializes the content of aNode. michael@0: nsresult SerializeToStringIterative(nsINode* aNode, michael@0: nsAString& aStr); michael@0: nsresult SerializeRangeToString(nsRange *aRange, michael@0: nsAString& aOutputString); michael@0: nsresult SerializeRangeNodes(nsRange* aRange, michael@0: nsINode* aNode, michael@0: nsAString& aString, michael@0: int32_t aDepth); michael@0: nsresult SerializeRangeContextStart(const nsTArray& aAncestorArray, michael@0: nsAString& aString); michael@0: nsresult SerializeRangeContextEnd(const nsTArray& aAncestorArray, michael@0: nsAString& aString); michael@0: virtual int32_t michael@0: GetImmediateContextCount(const nsTArray& aAncestorArray) michael@0: { michael@0: return -1; michael@0: } michael@0: michael@0: nsresult FlushText(nsAString& aString, bool aForce); michael@0: michael@0: bool IsVisibleNode(nsINode* aNode) michael@0: { michael@0: NS_PRECONDITION(aNode, ""); michael@0: michael@0: if (mFlags & SkipInvisibleContent) { michael@0: // Treat the visibility of the ShadowRoot as if it were michael@0: // the host content. michael@0: nsCOMPtr content; michael@0: ShadowRoot* shadowRoot = ShadowRoot::FromNode(aNode); michael@0: if (shadowRoot) { michael@0: content = shadowRoot->GetHost(); michael@0: } else { michael@0: content = do_QueryInterface(aNode); michael@0: } michael@0: michael@0: if (content) { michael@0: nsIFrame* frame = content->GetPrimaryFrame(); michael@0: if (!frame) { michael@0: if (aNode->IsNodeOfType(nsINode::eTEXT)) { michael@0: // We have already checked that our parent is visible. michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: bool isVisible = frame->StyleVisibility()->IsVisible(); michael@0: if (!isVisible && aNode->IsNodeOfType(nsINode::eTEXT)) michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool IsTag(nsIContent* aContent, nsIAtom* aAtom); michael@0: michael@0: virtual bool IncludeInContext(nsINode *aNode); michael@0: michael@0: nsCOMPtr mDocument; michael@0: nsCOMPtr mSelection; michael@0: nsRefPtr mRange; michael@0: nsCOMPtr mNode; michael@0: nsCOMPtr mStream; michael@0: nsCOMPtr mSerializer; michael@0: nsCOMPtr mUnicodeEncoder; michael@0: nsCOMPtr mCommonParent; michael@0: nsCOMPtr mNodeFixup; michael@0: nsCOMPtr mCharsetConverterManager; michael@0: michael@0: nsString mMimeType; michael@0: nsCString mCharset; michael@0: uint32_t mFlags; michael@0: uint32_t mWrapColumn; michael@0: uint32_t mStartDepth; michael@0: uint32_t mEndDepth; michael@0: int32_t mStartRootIndex; michael@0: int32_t mEndRootIndex; michael@0: nsAutoTArray mCommonAncestors; michael@0: nsAutoTArray mStartNodes; michael@0: nsAutoTArray mStartOffsets; michael@0: nsAutoTArray mEndNodes; michael@0: nsAutoTArray mEndOffsets; michael@0: bool mHaltRangeHint; michael@0: // Used when context has already been serialized for michael@0: // table cell selections (where parent is ) michael@0: bool mDisableContextSerialize; michael@0: bool mIsCopying; // Set to true only while copying michael@0: bool mNodeIsContainer; michael@0: nsStringBuffer* mCachedBuffer; michael@0: }; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocumentEncoder) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocumentEncoder) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocumentEncoder) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoder) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(nsDocumentEncoder, michael@0: mDocument, mSelection, mRange, mNode, mCommonParent) michael@0: michael@0: nsDocumentEncoder::nsDocumentEncoder() : mCachedBuffer(nullptr) michael@0: { michael@0: Initialize(); michael@0: mMimeType.AssignLiteral("text/plain"); michael@0: } michael@0: michael@0: void nsDocumentEncoder::Initialize(bool aClearCachedSerializer) michael@0: { michael@0: mFlags = 0; michael@0: mWrapColumn = 72; michael@0: mStartDepth = 0; michael@0: mEndDepth = 0; michael@0: mStartRootIndex = 0; michael@0: mEndRootIndex = 0; michael@0: mHaltRangeHint = false; michael@0: mDisableContextSerialize = false; michael@0: mNodeIsContainer = false; michael@0: if (aClearCachedSerializer) { michael@0: mSerializer = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsDocumentEncoder::~nsDocumentEncoder() michael@0: { michael@0: if (mCachedBuffer) { michael@0: mCachedBuffer->Release(); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::Init(nsIDOMDocument* aDocument, michael@0: const nsAString& aMimeType, michael@0: uint32_t aFlags) michael@0: { michael@0: if (!aDocument) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsCOMPtr doc = do_QueryInterface(aDocument); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); michael@0: michael@0: return NativeInit(doc, aMimeType, aFlags); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::NativeInit(nsIDocument* aDocument, michael@0: const nsAString& aMimeType, michael@0: uint32_t aFlags) michael@0: { michael@0: if (!aDocument) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: Initialize(!mMimeType.Equals(aMimeType)); michael@0: michael@0: mDocument = aDocument; michael@0: michael@0: mMimeType = aMimeType; michael@0: michael@0: mFlags = aFlags; michael@0: mIsCopying = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::SetWrapColumn(uint32_t aWC) michael@0: { michael@0: mWrapColumn = aWC; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::SetSelection(nsISelection* aSelection) michael@0: { michael@0: mSelection = aSelection; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::SetRange(nsIDOMRange* aRange) michael@0: { michael@0: mRange = static_cast(aRange); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::SetNode(nsIDOMNode* aNode) michael@0: { michael@0: mNodeIsContainer = false; michael@0: mNode = do_QueryInterface(aNode); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::SetNativeNode(nsINode* aNode) michael@0: { michael@0: mNodeIsContainer = false; michael@0: mNode = aNode; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::SetContainerNode(nsIDOMNode *aContainer) michael@0: { michael@0: mNodeIsContainer = true; michael@0: mNode = do_QueryInterface(aContainer); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::SetNativeContainerNode(nsINode* aContainer) michael@0: { michael@0: mNodeIsContainer = true; michael@0: mNode = aContainer; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::SetCharset(const nsACString& aCharset) michael@0: { michael@0: mCharset = aCharset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::GetMimeType(nsAString& aMimeType) michael@0: { michael@0: aMimeType = mMimeType; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsDocumentEncoder::IncludeInContext(nsINode *aNode) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: static michael@0: bool michael@0: IsInvisibleBreak(nsINode *aNode) { michael@0: // xxxehsan: we should probably figure out a way to determine michael@0: // if a BR node is visible without using the editor. michael@0: Element* elt = aNode->AsElement(); michael@0: if (!elt->IsHTML(nsGkAtoms::br) || michael@0: !aNode->IsEditable()) { michael@0: return false; michael@0: } michael@0: michael@0: // Grab the editor associated with the document michael@0: nsIDocument *doc = aNode->GetCurrentDoc(); michael@0: if (doc) { michael@0: nsPIDOMWindow *window = doc->GetWindow(); michael@0: if (window) { michael@0: nsIDocShell *docShell = window->GetDocShell(); michael@0: if (docShell) { michael@0: nsCOMPtr editor; michael@0: docShell->GetEditor(getter_AddRefs(editor)); michael@0: nsCOMPtr htmlEditor = do_QueryInterface(editor); michael@0: if (htmlEditor) { michael@0: bool isVisible = false; michael@0: nsCOMPtr domNode = do_QueryInterface(aNode); michael@0: htmlEditor->BreakIsVisible(domNode, &isVisible); michael@0: return !isVisible; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: nsDocumentEncoder::SerializeNodeStart(nsINode* aNode, michael@0: int32_t aStartOffset, michael@0: int32_t aEndOffset, michael@0: nsAString& aStr, michael@0: nsINode* aOriginalNode) michael@0: { michael@0: if (!IsVisibleNode(aNode)) michael@0: return NS_OK; michael@0: michael@0: nsINode* node = nullptr; michael@0: nsCOMPtr fixedNodeKungfuDeathGrip; michael@0: michael@0: // Caller didn't do fixup, so we'll do it ourselves michael@0: if (!aOriginalNode) { michael@0: aOriginalNode = aNode; michael@0: if (mNodeFixup) { michael@0: bool dummy; michael@0: nsCOMPtr domNodeIn = do_QueryInterface(aNode); michael@0: nsCOMPtr domNodeOut; michael@0: mNodeFixup->FixupNode(domNodeIn, &dummy, getter_AddRefs(domNodeOut)); michael@0: fixedNodeKungfuDeathGrip = do_QueryInterface(domNodeOut); michael@0: node = fixedNodeKungfuDeathGrip; michael@0: } michael@0: } michael@0: michael@0: // Either there was no fixed-up node, michael@0: // or the caller did fixup themselves and aNode is already fixed michael@0: if (!node) michael@0: node = aNode; michael@0: michael@0: if (node->IsElement()) { michael@0: if ((mFlags & (nsIDocumentEncoder::OutputPreformatted | michael@0: nsIDocumentEncoder::OutputDropInvisibleBreak)) && michael@0: IsInvisibleBreak(node)) { michael@0: return NS_OK; michael@0: } michael@0: Element* originalElement = michael@0: aOriginalNode && aOriginalNode->IsElement() ? michael@0: aOriginalNode->AsElement() : nullptr; michael@0: mSerializer->AppendElementStart(node->AsElement(), michael@0: originalElement, aStr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: switch (node->NodeType()) { michael@0: case nsIDOMNode::TEXT_NODE: michael@0: { michael@0: mSerializer->AppendText(static_cast(node), michael@0: aStartOffset, aEndOffset, aStr); michael@0: break; michael@0: } michael@0: case nsIDOMNode::CDATA_SECTION_NODE: michael@0: { michael@0: mSerializer->AppendCDATASection(static_cast(node), michael@0: aStartOffset, aEndOffset, aStr); michael@0: break; michael@0: } michael@0: case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: michael@0: { michael@0: mSerializer->AppendProcessingInstruction(static_cast(node), michael@0: aStartOffset, aEndOffset, aStr); michael@0: break; michael@0: } michael@0: case nsIDOMNode::COMMENT_NODE: michael@0: { michael@0: mSerializer->AppendComment(static_cast(node), michael@0: aStartOffset, aEndOffset, aStr); michael@0: break; michael@0: } michael@0: case nsIDOMNode::DOCUMENT_TYPE_NODE: michael@0: { michael@0: mSerializer->AppendDoctype(static_cast(node), aStr); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDocumentEncoder::SerializeNodeEnd(nsINode* aNode, michael@0: nsAString& aStr) michael@0: { michael@0: if (!IsVisibleNode(aNode)) michael@0: return NS_OK; michael@0: michael@0: if (aNode->IsElement()) { michael@0: mSerializer->AppendElementEnd(aNode->AsElement(), aStr); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode, michael@0: nsAString& aStr, michael@0: bool aDontSerializeRoot, michael@0: uint32_t aMaxLength) michael@0: { michael@0: if (aMaxLength > 0 && aStr.Length() >= aMaxLength) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!IsVisibleNode(aNode)) michael@0: return NS_OK; michael@0: michael@0: nsresult rv = NS_OK; michael@0: bool serializeClonedChildren = false; michael@0: nsINode* maybeFixedNode = nullptr; michael@0: michael@0: // Keep the node from FixupNode alive. michael@0: nsCOMPtr fixedNodeKungfuDeathGrip; michael@0: if (mNodeFixup) { michael@0: nsCOMPtr domNodeIn = do_QueryInterface(aNode); michael@0: nsCOMPtr domNodeOut; michael@0: mNodeFixup->FixupNode(domNodeIn, &serializeClonedChildren, getter_AddRefs(domNodeOut)); michael@0: fixedNodeKungfuDeathGrip = do_QueryInterface(domNodeOut); michael@0: maybeFixedNode = fixedNodeKungfuDeathGrip; michael@0: } michael@0: michael@0: if (!maybeFixedNode) michael@0: maybeFixedNode = aNode; michael@0: michael@0: if ((mFlags & SkipInvisibleContent) && michael@0: !(mFlags & OutputNonTextContentAsPlaceholder)) { michael@0: if (aNode->IsNodeOfType(nsINode::eCONTENT)) { michael@0: nsIFrame* frame = static_cast(aNode)->GetPrimaryFrame(); michael@0: if (frame) { michael@0: bool isSelectable; michael@0: frame->IsSelectable(&isSelectable, nullptr); michael@0: if (!isSelectable){ michael@0: aDontSerializeRoot = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!aDontSerializeRoot) { michael@0: int32_t endOffset = -1; michael@0: if (aMaxLength > 0) { michael@0: MOZ_ASSERT(aMaxLength >= aStr.Length()); michael@0: endOffset = aMaxLength - aStr.Length(); michael@0: } michael@0: rv = SerializeNodeStart(maybeFixedNode, 0, endOffset, aStr, aNode); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsINode* node = serializeClonedChildren ? maybeFixedNode : aNode; michael@0: michael@0: for (nsINode* child = nsNodeUtils::GetFirstChildOfTemplateOrNode(node); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: rv = SerializeToStringRecursive(child, aStr, false, aMaxLength); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (!aDontSerializeRoot) { michael@0: rv = SerializeNodeEnd(node, aStr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return FlushText(aStr, false); michael@0: } michael@0: michael@0: nsresult michael@0: nsDocumentEncoder::SerializeToStringIterative(nsINode* aNode, michael@0: nsAString& aStr) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsINode* node = aNode->GetFirstChild(); michael@0: while (node) { michael@0: nsINode* current = node; michael@0: rv = SerializeNodeStart(current, 0, -1, aStr, current); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: node = current->GetFirstChild(); michael@0: while (!node && current && current != aNode) { michael@0: rv = SerializeNodeEnd(current, aStr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // Check if we have siblings. michael@0: node = current->GetNextSibling(); michael@0: if (!node) { michael@0: // Perhaps parent node has siblings. michael@0: current = current->GetParentNode(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsDocumentEncoder::IsTag(nsIContent* aContent, nsIAtom* aAtom) michael@0: { michael@0: return aContent && aContent->Tag() == aAtom; michael@0: } michael@0: michael@0: static nsresult michael@0: ConvertAndWrite(const nsAString& aString, michael@0: nsIOutputStream* aStream, michael@0: nsIUnicodeEncoder* aEncoder) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aStream); michael@0: NS_ENSURE_ARG_POINTER(aEncoder); michael@0: nsresult rv; michael@0: int32_t charLength, startCharLength; michael@0: const nsPromiseFlatString& flat = PromiseFlatString(aString); michael@0: const char16_t* unicodeBuf = flat.get(); michael@0: int32_t unicodeLength = aString.Length(); michael@0: int32_t startLength = unicodeLength; michael@0: michael@0: rv = aEncoder->GetMaxLength(unicodeBuf, unicodeLength, &charLength); michael@0: startCharLength = charLength; michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!charLength) { michael@0: // Nothing to write. Besides, a length 0 string has an immutable buffer, so michael@0: // attempts to null-terminate it will crash. michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString charXferString; michael@0: if (!charXferString.SetLength(charLength, fallible_t())) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: char* charXferBuf = charXferString.BeginWriting(); michael@0: nsresult convert_rv = NS_OK; michael@0: michael@0: do { michael@0: unicodeLength = startLength; michael@0: charLength = startCharLength; michael@0: michael@0: convert_rv = aEncoder->Convert(unicodeBuf, &unicodeLength, charXferBuf, &charLength); michael@0: NS_ENSURE_SUCCESS(convert_rv, convert_rv); michael@0: michael@0: // Make sure charXferBuf is null-terminated before we call michael@0: // Write(). michael@0: michael@0: charXferBuf[charLength] = '\0'; michael@0: michael@0: uint32_t written; michael@0: rv = aStream->Write(charXferBuf, charLength, &written); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If the converter couldn't convert a chraacer we replace the michael@0: // character with a characre entity. michael@0: if (convert_rv == NS_ERROR_UENC_NOMAPPING) { michael@0: // Finishes the conversion. michael@0: // The converter has the possibility to write some extra data and flush its final state. michael@0: char finish_buf[33]; michael@0: charLength = sizeof(finish_buf) - 1; michael@0: rv = aEncoder->Finish(finish_buf, &charLength); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Make sure finish_buf is null-terminated before we call michael@0: // Write(). michael@0: michael@0: finish_buf[charLength] = '\0'; michael@0: michael@0: rv = aStream->Write(finish_buf, charLength, &written); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString entString("&#"); michael@0: if (NS_IS_HIGH_SURROGATE(unicodeBuf[unicodeLength - 1]) && michael@0: unicodeLength < startLength && NS_IS_LOW_SURROGATE(unicodeBuf[unicodeLength])) { michael@0: entString.AppendInt(SURROGATE_TO_UCS4(unicodeBuf[unicodeLength - 1], michael@0: unicodeBuf[unicodeLength])); michael@0: unicodeLength += 1; michael@0: } michael@0: else michael@0: entString.AppendInt(unicodeBuf[unicodeLength - 1]); michael@0: entString.Append(';'); michael@0: michael@0: // Since entString is an nsAutoCString we know entString.get() michael@0: // returns a null-terminated string, so no need for extra michael@0: // null-termination before calling Write() here. michael@0: michael@0: rv = aStream->Write(entString.get(), entString.Length(), &written); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: unicodeBuf += unicodeLength; michael@0: startLength -= unicodeLength; michael@0: } michael@0: } while (convert_rv == NS_ERROR_UENC_NOMAPPING); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsDocumentEncoder::FlushText(nsAString& aString, bool aForce) michael@0: { michael@0: if (!mStream) michael@0: return NS_OK; michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (aString.Length() > 1024 || aForce) { michael@0: rv = ConvertAndWrite(aString, mStream, mUnicodeEncoder); michael@0: michael@0: aString.Truncate(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: #if 0 // This code is really fast at serializing a range, but unfortunately michael@0: // there are problems with it so we don't use it now, maybe later... michael@0: static nsresult ChildAt(nsIDOMNode* aNode, int32_t aIndex, nsIDOMNode*& aChild) michael@0: { michael@0: nsCOMPtr content(do_QueryInterface(aNode)); michael@0: michael@0: aChild = nullptr; michael@0: michael@0: NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); michael@0: michael@0: nsIContent *child = content->GetChildAt(aIndex); michael@0: michael@0: if (child) michael@0: return CallQueryInterface(child, &aChild); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static int32_t IndexOf(nsIDOMNode* aParent, nsIDOMNode* aChild) michael@0: { michael@0: nsCOMPtr parent(do_QueryInterface(aParent)); michael@0: nsCOMPtr child(do_QueryInterface(aChild)); michael@0: michael@0: if (!parent) michael@0: return -1; michael@0: michael@0: return parent->IndexOf(child); michael@0: } michael@0: michael@0: static inline int32_t GetIndex(nsTArray& aIndexArray) michael@0: { michael@0: int32_t count = aIndexArray.Length(); michael@0: michael@0: if (count) { michael@0: return aIndexArray.ElementAt(count - 1); michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static nsresult GetNextNode(nsIDOMNode* aNode, nsTArray& aIndexArray, michael@0: nsIDOMNode*& aNextNode, michael@0: nsRangeIterationDirection& aDirection) michael@0: { michael@0: bool hasChildren; michael@0: michael@0: aNextNode = nullptr; michael@0: michael@0: aNode->HasChildNodes(&hasChildren); michael@0: michael@0: if (hasChildren && aDirection == kDirectionIn) { michael@0: ChildAt(aNode, 0, aNextNode); michael@0: NS_ENSURE_TRUE(aNextNode, NS_ERROR_FAILURE); michael@0: michael@0: aIndexArray.AppendElement(0); michael@0: michael@0: aDirection = kDirectionIn; michael@0: } else if (aDirection == kDirectionIn) { michael@0: aNextNode = aNode; michael@0: michael@0: NS_ADDREF(aNextNode); michael@0: michael@0: aDirection = kDirectionOut; michael@0: } else { michael@0: nsCOMPtr parent; michael@0: michael@0: aNode->GetParentNode(getter_AddRefs(parent)); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE); michael@0: michael@0: int32_t count = aIndexArray.Length(); michael@0: michael@0: if (count) { michael@0: int32_t indx = aIndexArray.ElementAt(count - 1); michael@0: michael@0: ChildAt(parent, indx + 1, aNextNode); michael@0: michael@0: if (aNextNode) michael@0: aIndexArray.ElementAt(count - 1) = indx + 1; michael@0: else michael@0: aIndexArray.RemoveElementAt(count - 1); michael@0: } else { michael@0: int32_t indx = IndexOf(parent, aNode); michael@0: michael@0: if (indx >= 0) { michael@0: ChildAt(parent, indx + 1, aNextNode); michael@0: michael@0: if (aNextNode) michael@0: aIndexArray.AppendElement(indx + 1); michael@0: } michael@0: } michael@0: michael@0: if (aNextNode) { michael@0: aDirection = kDirectionIn; michael@0: } else { michael@0: aDirection = kDirectionOut; michael@0: michael@0: aNextNode = parent; michael@0: michael@0: NS_ADDREF(aNextNode); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: static bool IsTextNode(nsINode *aNode) michael@0: { michael@0: return aNode && aNode->IsNodeOfType(nsINode::eTEXT); michael@0: } michael@0: michael@0: nsresult michael@0: nsDocumentEncoder::SerializeRangeNodes(nsRange* aRange, michael@0: nsINode* aNode, michael@0: nsAString& aString, michael@0: int32_t aDepth) michael@0: { michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); michael@0: michael@0: if (!IsVisibleNode(aNode)) michael@0: return NS_OK; michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: // get start and end nodes for this recursion level michael@0: nsCOMPtr startNode, endNode; michael@0: { michael@0: int32_t start = mStartRootIndex - aDepth; michael@0: if (start >= 0 && (uint32_t)start <= mStartNodes.Length()) michael@0: startNode = mStartNodes[start]; michael@0: michael@0: int32_t end = mEndRootIndex - aDepth; michael@0: if (end >= 0 && (uint32_t)end <= mEndNodes.Length()) michael@0: endNode = mEndNodes[end]; michael@0: } michael@0: michael@0: if (startNode != content && endNode != content) michael@0: { michael@0: // node is completely contained in range. Serialize the whole subtree michael@0: // rooted by this node. michael@0: rv = SerializeToStringRecursive(aNode, aString, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else michael@0: { michael@0: // due to implementation it is impossible for text node to be both start and end of michael@0: // range. We would have handled that case without getting here. michael@0: //XXXsmaug What does this all mean? michael@0: if (IsTextNode(aNode)) michael@0: { michael@0: if (startNode == content) michael@0: { michael@0: int32_t startOffset = aRange->StartOffset(); michael@0: rv = SerializeNodeStart(aNode, startOffset, -1, aString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else michael@0: { michael@0: int32_t endOffset = aRange->EndOffset(); michael@0: rv = SerializeNodeStart(aNode, 0, endOffset, aString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: if (aNode != mCommonParent) michael@0: { michael@0: if (IncludeInContext(aNode)) michael@0: { michael@0: // halt the incrementing of mStartDepth/mEndDepth. This is michael@0: // so paste client will include this node in paste. michael@0: mHaltRangeHint = true; michael@0: } michael@0: if ((startNode == content) && !mHaltRangeHint) mStartDepth++; michael@0: if ((endNode == content) && !mHaltRangeHint) mEndDepth++; michael@0: michael@0: // serialize the start of this node michael@0: rv = SerializeNodeStart(aNode, 0, -1, aString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // do some calculations that will tell us which children of this michael@0: // node are in the range. michael@0: nsIContent* childAsNode = nullptr; michael@0: int32_t startOffset = 0, endOffset = -1; michael@0: if (startNode == content && mStartRootIndex >= aDepth) michael@0: startOffset = mStartOffsets[mStartRootIndex - aDepth]; michael@0: if (endNode == content && mEndRootIndex >= aDepth) michael@0: endOffset = mEndOffsets[mEndRootIndex - aDepth]; michael@0: // generated content will cause offset values of -1 to be returned. michael@0: int32_t j; michael@0: uint32_t childCount = content->GetChildCount(); michael@0: michael@0: if (startOffset == -1) startOffset = 0; michael@0: if (endOffset == -1) endOffset = childCount; michael@0: else michael@0: { michael@0: // if we are at the "tip" of the selection, endOffset is fine. michael@0: // otherwise, we need to add one. This is because of the semantics michael@0: // of the offset list created by GetAncestorsAndOffsets(). The michael@0: // intermediate points on the list use the endOffset of the michael@0: // location of the ancestor, rather than just past it. So we need michael@0: // to add one here in order to include it in the children we serialize. michael@0: if (aNode != aRange->GetEndParent()) michael@0: { michael@0: endOffset++; michael@0: } michael@0: } michael@0: // serialize the children of this node that are in the range michael@0: for (j=startOffset; jGetChildAt(j); michael@0: michael@0: if ((j==startOffset) || (j==endOffset-1)) michael@0: rv = SerializeRangeNodes(aRange, childAsNode, aString, aDepth+1); michael@0: else michael@0: rv = SerializeToStringRecursive(childAsNode, aString, false); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // serialize the end of this node michael@0: if (aNode != mCommonParent) michael@0: { michael@0: rv = SerializeNodeEnd(aNode, aString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDocumentEncoder::SerializeRangeContextStart(const nsTArray& aAncestorArray, michael@0: nsAString& aString) michael@0: { michael@0: if (mDisableContextSerialize) { michael@0: return NS_OK; michael@0: } michael@0: int32_t i = aAncestorArray.Length(), j; michael@0: nsresult rv = NS_OK; michael@0: michael@0: // currently only for table-related elements; see Bug 137450 michael@0: j = GetImmediateContextCount(aAncestorArray); michael@0: michael@0: while (i > 0) { michael@0: nsINode *node = aAncestorArray.ElementAt(--i); michael@0: michael@0: if (!node) michael@0: break; michael@0: michael@0: // Either a general inclusion or as immediate context michael@0: if (IncludeInContext(node) || i < j) { michael@0: rv = SerializeNodeStart(node, 0, -1, aString); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsDocumentEncoder::SerializeRangeContextEnd(const nsTArray& aAncestorArray, michael@0: nsAString& aString) michael@0: { michael@0: if (mDisableContextSerialize) { michael@0: return NS_OK; michael@0: } michael@0: int32_t i = 0, j; michael@0: int32_t count = aAncestorArray.Length(); michael@0: nsresult rv = NS_OK; michael@0: michael@0: // currently only for table-related elements michael@0: j = GetImmediateContextCount(aAncestorArray); michael@0: michael@0: while (i < count) { michael@0: nsINode *node = aAncestorArray.ElementAt(i++); michael@0: michael@0: if (!node) michael@0: break; michael@0: michael@0: // Either a general inclusion or as immediate context michael@0: if (IncludeInContext(node) || i - 1 < j) { michael@0: rv = SerializeNodeEnd(node, aString); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsDocumentEncoder::SerializeRangeToString(nsRange *aRange, michael@0: nsAString& aOutputString) michael@0: { michael@0: if (!aRange || aRange->Collapsed()) michael@0: return NS_OK; michael@0: michael@0: mCommonParent = aRange->GetCommonAncestor(); michael@0: michael@0: if (!mCommonParent) michael@0: return NS_OK; michael@0: michael@0: nsINode* startParent = aRange->GetStartParent(); michael@0: NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE); michael@0: int32_t startOffset = aRange->StartOffset(); michael@0: michael@0: nsINode* endParent = aRange->GetEndParent(); michael@0: NS_ENSURE_TRUE(endParent, NS_ERROR_FAILURE); michael@0: int32_t endOffset = aRange->EndOffset(); michael@0: michael@0: mCommonAncestors.Clear(); michael@0: mStartNodes.Clear(); michael@0: mStartOffsets.Clear(); michael@0: mEndNodes.Clear(); michael@0: mEndOffsets.Clear(); michael@0: michael@0: nsContentUtils::GetAncestors(mCommonParent, mCommonAncestors); michael@0: nsCOMPtr sp = do_QueryInterface(startParent); michael@0: nsContentUtils::GetAncestorsAndOffsets(sp, startOffset, michael@0: &mStartNodes, &mStartOffsets); michael@0: nsCOMPtr ep = do_QueryInterface(endParent); michael@0: nsContentUtils::GetAncestorsAndOffsets(ep, endOffset, michael@0: &mEndNodes, &mEndOffsets); michael@0: michael@0: nsCOMPtr commonContent = do_QueryInterface(mCommonParent); michael@0: mStartRootIndex = mStartNodes.IndexOf(commonContent); michael@0: mEndRootIndex = mEndNodes.IndexOf(commonContent); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: rv = SerializeRangeContextStart(mCommonAncestors, aOutputString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if ((startParent == endParent) && IsTextNode(startParent)) michael@0: { michael@0: if (mFlags & SkipInvisibleContent) { michael@0: // Check that the parent is visible if we don't a frame. michael@0: // IsVisibleNode() will do it when there's a frame. michael@0: nsCOMPtr content = do_QueryInterface(startParent); michael@0: if (content && !content->GetPrimaryFrame()) { michael@0: nsIContent* parent = content->GetParent(); michael@0: if (!parent || !IsVisibleNode(parent)) michael@0: return NS_OK; michael@0: } michael@0: } michael@0: rv = SerializeNodeStart(startParent, startOffset, endOffset, aOutputString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else michael@0: { michael@0: rv = SerializeRangeNodes(aRange, mCommonParent, aOutputString, 0); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: rv = SerializeRangeContextEnd(mCommonAncestors, aOutputString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::EncodeToString(nsAString& aOutputString) michael@0: { michael@0: return EncodeToStringWithMaxLength(0, aOutputString); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength, michael@0: nsAString& aOutputString) michael@0: { michael@0: if (!mDocument) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: aOutputString.Truncate(); michael@0: michael@0: nsString output; michael@0: static const size_t bufferSize = 2048; michael@0: if (!mCachedBuffer) { michael@0: mCachedBuffer = nsStringBuffer::Alloc(bufferSize).take(); michael@0: } michael@0: NS_ASSERTION(!mCachedBuffer->IsReadonly(), michael@0: "DocumentEncoder shouldn't keep reference to non-readonly buffer!"); michael@0: static_cast(mCachedBuffer->Data())[0] = char16_t(0); michael@0: mCachedBuffer->ToString(0, output, true); michael@0: // output owns the buffer now! michael@0: mCachedBuffer = nullptr; michael@0: michael@0: michael@0: if (!mSerializer) { michael@0: nsAutoCString progId(NS_CONTENTSERIALIZER_CONTRACTID_PREFIX); michael@0: AppendUTF16toUTF8(mMimeType, progId); michael@0: michael@0: mSerializer = do_CreateInstance(progId.get()); michael@0: NS_ENSURE_TRUE(mSerializer, NS_ERROR_NOT_IMPLEMENTED); michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsCOMPtr charsetAtom; michael@0: if (!mCharset.IsEmpty()) { michael@0: if (!mCharsetConverterManager) { michael@0: mCharsetConverterManager = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: bool rewriteEncodingDeclaration = !(mSelection || mRange || mNode) && !(mFlags & OutputDontRewriteEncodingDeclaration); michael@0: mSerializer->Init(mFlags, mWrapColumn, mCharset.get(), mIsCopying, rewriteEncodingDeclaration); michael@0: michael@0: if (mSelection) { michael@0: nsCOMPtr range; michael@0: int32_t i, count = 0; michael@0: michael@0: rv = mSelection->GetRangeCount(&count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr node, prevNode; michael@0: for (i = 0; i < count; i++) { michael@0: mSelection->GetRangeAt(i, getter_AddRefs(range)); michael@0: michael@0: // Bug 236546: newlines not added when copying table cells into clipboard michael@0: // Each selected cell shows up as a range containing a row with a single cell michael@0: // get the row, compare it to previous row and emit as needed michael@0: // Bug 137450: Problem copying/pasting a table from a web page to Excel. michael@0: // Each separate block of produced above will be wrapped by the michael@0: // immediate context. This assumes that you can't select cells that are michael@0: // multiple selections from two tables simultaneously. michael@0: range->GetStartContainer(getter_AddRefs(node)); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: if (node != prevNode) { michael@0: nsCOMPtr p; michael@0: if (prevNode) { michael@0: p = do_QueryInterface(prevNode); michael@0: rv = SerializeNodeEnd(p, output); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: nsCOMPtr content = do_QueryInterface(node); michael@0: if (content && content->IsHTML(nsGkAtoms::tr)) { michael@0: nsINode* n = content; michael@0: if (!prevNode) { michael@0: // Went from a non- to a michael@0: mCommonAncestors.Clear(); michael@0: nsContentUtils::GetAncestors(n->GetParentNode(), mCommonAncestors); michael@0: rv = SerializeRangeContextStart(mCommonAncestors, output); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // Don't let SerializeRangeToString serialize the context again michael@0: mDisableContextSerialize = true; michael@0: } michael@0: michael@0: rv = SerializeNodeStart(n, 0, -1, output); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: prevNode = node; michael@0: } else if (prevNode) { michael@0: // Went from a to a non- michael@0: mCommonAncestors.Clear(); michael@0: nsContentUtils::GetAncestors(p->GetParentNode(), mCommonAncestors); michael@0: mDisableContextSerialize = false; michael@0: rv = SerializeRangeContextEnd(mCommonAncestors, output); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: prevNode = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsRange* r = static_cast(range.get()); michael@0: rv = SerializeRangeToString(r, output); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (prevNode) { michael@0: nsCOMPtr p = do_QueryInterface(prevNode); michael@0: rv = SerializeNodeEnd(p, output); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mCommonAncestors.Clear(); michael@0: nsContentUtils::GetAncestors(p->GetParentNode(), mCommonAncestors); michael@0: mDisableContextSerialize = false; michael@0: rv = SerializeRangeContextEnd(mCommonAncestors, output); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Just to be safe michael@0: mDisableContextSerialize = false; michael@0: michael@0: mSelection = nullptr; michael@0: } else if (mRange) { michael@0: rv = SerializeRangeToString(mRange, output); michael@0: michael@0: mRange = nullptr; michael@0: } else if (mNode) { michael@0: if (!mNodeFixup && !(mFlags & SkipInvisibleContent) && !mStream && michael@0: mNodeIsContainer) { michael@0: rv = SerializeToStringIterative(mNode, output); michael@0: } else { michael@0: rv = SerializeToStringRecursive(mNode, output, mNodeIsContainer); michael@0: } michael@0: mNode = nullptr; michael@0: } else { michael@0: rv = mSerializer->AppendDocumentStart(mDocument, output); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = SerializeToStringRecursive(mDocument, output, false, aMaxLength); michael@0: } michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mSerializer->Flush(output); michael@0: michael@0: mCachedBuffer = nsStringBuffer::FromString(output); michael@0: // We have to be careful how we set aOutputString, because we don't michael@0: // want it to end up sharing mCachedBuffer if we plan to reuse it. michael@0: bool setOutput = false; michael@0: // Try to cache the buffer. michael@0: if (mCachedBuffer) { michael@0: if (mCachedBuffer->StorageSize() == bufferSize && michael@0: !mCachedBuffer->IsReadonly()) { michael@0: mCachedBuffer->AddRef(); michael@0: } else { michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mCachedBuffer->ToString(output.Length(), aOutputString); michael@0: setOutput = true; michael@0: } michael@0: mCachedBuffer = nullptr; michael@0: } michael@0: } michael@0: michael@0: if (!setOutput && NS_SUCCEEDED(rv)) { michael@0: aOutputString.Append(output.get(), output.Length()); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::EncodeToStream(nsIOutputStream* aStream) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mDocument) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: if (!mCharsetConverterManager) { michael@0: mCharsetConverterManager = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = mCharsetConverterManager->GetUnicodeEncoder(mCharset.get(), michael@0: getter_AddRefs(mUnicodeEncoder)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (mMimeType.LowerCaseEqualsLiteral("text/plain")) { michael@0: rv = mUnicodeEncoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace, nullptr, '?'); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mStream = aStream; michael@0: michael@0: nsAutoString buf; michael@0: michael@0: rv = EncodeToString(buf); michael@0: michael@0: // Force a flush of the last chunk of data. michael@0: FlushText(buf, true); michael@0: michael@0: mStream = nullptr; michael@0: mUnicodeEncoder = nullptr; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::EncodeToStringWithContext(nsAString& aContextString, michael@0: nsAString& aInfoString, michael@0: nsAString& aEncodedString) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentEncoder::SetNodeFixup(nsIDocumentEncoderNodeFixup *aFixup) michael@0: { michael@0: mNodeFixup = aFixup; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult NS_NewTextEncoder(nsIDocumentEncoder** aResult); // make mac compiler happy michael@0: michael@0: nsresult michael@0: NS_NewTextEncoder(nsIDocumentEncoder** aResult) michael@0: { michael@0: *aResult = new nsDocumentEncoder; michael@0: if (!*aResult) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(*aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: class nsHTMLCopyEncoder : public nsDocumentEncoder michael@0: { michael@0: public: michael@0: michael@0: nsHTMLCopyEncoder(); michael@0: virtual ~nsHTMLCopyEncoder(); michael@0: michael@0: NS_IMETHOD Init(nsIDOMDocument* aDocument, const nsAString& aMimeType, uint32_t aFlags); michael@0: michael@0: // overridden methods from nsDocumentEncoder michael@0: NS_IMETHOD SetSelection(nsISelection* aSelection); michael@0: NS_IMETHOD EncodeToStringWithContext(nsAString& aContextString, michael@0: nsAString& aInfoString, michael@0: nsAString& aEncodedString); michael@0: NS_IMETHOD EncodeToString(nsAString& aOutputString); michael@0: michael@0: protected: michael@0: michael@0: enum Endpoint michael@0: { michael@0: kStart, michael@0: kEnd michael@0: }; michael@0: michael@0: nsresult PromoteRange(nsIDOMRange *inRange); michael@0: nsresult PromoteAncestorChain(nsCOMPtr *ioNode, michael@0: int32_t *ioStartOffset, michael@0: int32_t *ioEndOffset); michael@0: nsresult GetPromotedPoint(Endpoint aWhere, nsIDOMNode *aNode, int32_t aOffset, michael@0: nsCOMPtr *outNode, int32_t *outOffset, nsIDOMNode *aCommon); michael@0: nsCOMPtr GetChildAt(nsIDOMNode *aParent, int32_t aOffset); michael@0: bool IsMozBR(nsIDOMNode* aNode); michael@0: nsresult GetNodeLocation(nsIDOMNode *inChild, nsCOMPtr *outParent, int32_t *outOffset); michael@0: bool IsRoot(nsIDOMNode* aNode); michael@0: bool IsFirstNode(nsIDOMNode *aNode); michael@0: bool IsLastNode(nsIDOMNode *aNode); michael@0: bool IsEmptyTextContent(nsIDOMNode* aNode); michael@0: virtual bool IncludeInContext(nsINode *aNode); michael@0: virtual int32_t michael@0: GetImmediateContextCount(const nsTArray& aAncestorArray); michael@0: michael@0: bool mIsTextWidget; michael@0: }; michael@0: michael@0: nsHTMLCopyEncoder::nsHTMLCopyEncoder() michael@0: { michael@0: mIsTextWidget = false; michael@0: } michael@0: michael@0: nsHTMLCopyEncoder::~nsHTMLCopyEncoder() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLCopyEncoder::Init(nsIDOMDocument* aDocument, michael@0: const nsAString& aMimeType, michael@0: uint32_t aFlags) michael@0: { michael@0: if (!aDocument) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: mIsTextWidget = false; michael@0: Initialize(); michael@0: michael@0: mIsCopying = true; michael@0: mDocument = do_QueryInterface(aDocument); michael@0: NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE); michael@0: michael@0: // Hack, hack! Traditionally, the caller passes text/unicode, which is michael@0: // treated as "guess text/html or text/plain" in this context. (It has a michael@0: // different meaning in other contexts. Sigh.) From now on, "text/plain" michael@0: // means forcing text/plain instead of guessing. michael@0: if (aMimeType.EqualsLiteral("text/plain")) { michael@0: mMimeType.AssignLiteral("text/plain"); michael@0: } else { michael@0: mMimeType.AssignLiteral("text/html"); michael@0: } michael@0: michael@0: // Make all links absolute when copying michael@0: // (see related bugs #57296, #41924, #58646, #32768) michael@0: mFlags = aFlags | OutputAbsoluteLinks; michael@0: michael@0: if (!mDocument->IsScriptEnabled()) michael@0: mFlags |= OutputNoScriptContent; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLCopyEncoder::SetSelection(nsISelection* aSelection) michael@0: { michael@0: // check for text widgets: we need to recognize these so that michael@0: // we don't tweak the selection to be outside of the magic michael@0: // div that ender-lite text widgets are embedded in. michael@0: michael@0: if (!aSelection) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsCOMPtr range; michael@0: nsCOMPtr commonParent; michael@0: Selection* selection = static_cast(aSelection); michael@0: uint32_t rangeCount = selection->GetRangeCount(); michael@0: michael@0: // if selection is uninitialized return michael@0: if (!rangeCount) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // we'll just use the common parent of the first range. Implicit assumption michael@0: // here that multi-range selections are table cell selections, in which case michael@0: // the common parent is somewhere in the table and we don't really care where. michael@0: nsresult rv = aSelection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!range) michael@0: return NS_ERROR_NULL_POINTER; michael@0: range->GetCommonAncestorContainer(getter_AddRefs(commonParent)); michael@0: michael@0: for (nsCOMPtr selContent(do_QueryInterface(commonParent)); michael@0: selContent; michael@0: selContent = selContent->GetParent()) michael@0: { michael@0: // checking for selection inside a plaintext form widget michael@0: nsIAtom *atom = selContent->Tag(); michael@0: if (atom == nsGkAtoms::input || michael@0: atom == nsGkAtoms::textarea) michael@0: { michael@0: mIsTextWidget = true; michael@0: break; michael@0: } michael@0: else if (atom == nsGkAtoms::body) michael@0: { michael@0: // check for moz prewrap style on body. If it's there we are michael@0: // in a plaintext editor. This is pretty cheezy but I haven't michael@0: // found a good way to tell if we are in a plaintext editor. michael@0: nsCOMPtr bodyElem = do_QueryInterface(selContent); michael@0: nsAutoString wsVal; michael@0: rv = bodyElem->GetAttribute(NS_LITERAL_STRING("style"), wsVal); michael@0: if (NS_SUCCEEDED(rv) && (kNotFound != wsVal.Find(NS_LITERAL_STRING("pre-wrap")))) michael@0: { michael@0: mIsTextWidget = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // normalize selection if we are not in a widget michael@0: if (mIsTextWidget) michael@0: { michael@0: mSelection = aSelection; michael@0: mMimeType.AssignLiteral("text/plain"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // also consider ourselves in a text widget if we can't find an html document michael@0: nsCOMPtr htmlDoc = do_QueryInterface(mDocument); michael@0: if (!(htmlDoc && mDocument->IsHTML())) { michael@0: mIsTextWidget = true; michael@0: mSelection = aSelection; michael@0: // mMimeType is set to text/plain when encoding starts. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // there's no Clone() for selection! fix... michael@0: //nsresult rv = aSelection->Clone(getter_AddRefs(mSelection); michael@0: //NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_NewDomSelection(getter_AddRefs(mSelection)); michael@0: NS_ENSURE_TRUE(mSelection, NS_ERROR_FAILURE); michael@0: michael@0: // loop thru the ranges in the selection michael@0: for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { michael@0: range = selection->GetRangeAt(rangeIdx); michael@0: NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); michael@0: nsCOMPtr myRange; michael@0: range->CloneRange(getter_AddRefs(myRange)); michael@0: NS_ENSURE_TRUE(myRange, NS_ERROR_FAILURE); michael@0: michael@0: // adjust range to include any ancestors who's children are entirely selected michael@0: rv = PromoteRange(myRange); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mSelection->AddRange(myRange); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLCopyEncoder::EncodeToString(nsAString& aOutputString) michael@0: { michael@0: if (mIsTextWidget) { michael@0: mMimeType.AssignLiteral("text/plain"); michael@0: } michael@0: return nsDocumentEncoder::EncodeToString(aOutputString); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLCopyEncoder::EncodeToStringWithContext(nsAString& aContextString, michael@0: nsAString& aInfoString, michael@0: nsAString& aEncodedString) michael@0: { michael@0: nsresult rv = EncodeToString(aEncodedString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // do not encode any context info or range hints if we are in a text widget. michael@0: if (mIsTextWidget) return NS_OK; michael@0: michael@0: // now encode common ancestors into aContextString. Note that the common ancestors michael@0: // will be for the last range in the selection in the case of multirange selections. michael@0: // encoding ancestors every range in a multirange selection in a way that could be michael@0: // understood by the paste code would be a lot more work to do. As a practical matter, michael@0: // selections are single range, and the ones that aren't are table cell selections michael@0: // where all the cells are in the same table. michael@0: michael@0: // leaf of ancestors might be text node. If so discard it. michael@0: int32_t count = mCommonAncestors.Length(); michael@0: int32_t i; michael@0: nsCOMPtr node; michael@0: if (count > 0) michael@0: node = mCommonAncestors.ElementAt(0); michael@0: michael@0: if (node && IsTextNode(node)) michael@0: { michael@0: mCommonAncestors.RemoveElementAt(0); michael@0: // don't forget to adjust range depth info michael@0: if (mStartDepth) mStartDepth--; michael@0: if (mEndDepth) mEndDepth--; michael@0: // and the count michael@0: count--; michael@0: } michael@0: michael@0: i = count; michael@0: while (i > 0) michael@0: { michael@0: node = mCommonAncestors.ElementAt(--i); michael@0: SerializeNodeStart(node, 0, -1, aContextString); michael@0: } michael@0: //i = 0; guaranteed by above michael@0: while (i < count) michael@0: { michael@0: node = mCommonAncestors.ElementAt(i++); michael@0: SerializeNodeEnd(node, aContextString); michael@0: } michael@0: michael@0: // encode range info : the start and end depth of the selection, where the depth is michael@0: // distance down in the parent hierarchy. Later we will need to add leading/trailing michael@0: // whitespace info to this. michael@0: nsAutoString infoString; michael@0: infoString.AppendInt(mStartDepth); michael@0: infoString.Append(char16_t(',')); michael@0: infoString.AppendInt(mEndDepth); michael@0: aInfoString = infoString; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsHTMLCopyEncoder::IncludeInContext(nsINode *aNode) michael@0: { michael@0: nsCOMPtr content(do_QueryInterface(aNode)); michael@0: michael@0: if (!content) michael@0: return false; michael@0: michael@0: nsIAtom *tag = content->Tag(); michael@0: michael@0: return (tag == nsGkAtoms::b || michael@0: tag == nsGkAtoms::i || michael@0: tag == nsGkAtoms::u || michael@0: tag == nsGkAtoms::a || michael@0: tag == nsGkAtoms::tt || michael@0: tag == nsGkAtoms::s || michael@0: tag == nsGkAtoms::big || michael@0: tag == nsGkAtoms::small || michael@0: tag == nsGkAtoms::strike || michael@0: tag == nsGkAtoms::em || michael@0: tag == nsGkAtoms::strong || michael@0: tag == nsGkAtoms::dfn || michael@0: tag == nsGkAtoms::code || michael@0: tag == nsGkAtoms::cite || michael@0: tag == nsGkAtoms::var || michael@0: tag == nsGkAtoms::abbr || michael@0: tag == nsGkAtoms::font || michael@0: tag == nsGkAtoms::script || michael@0: tag == nsGkAtoms::span || michael@0: tag == nsGkAtoms::pre || michael@0: tag == nsGkAtoms::h1 || michael@0: tag == nsGkAtoms::h2 || michael@0: tag == nsGkAtoms::h3 || michael@0: tag == nsGkAtoms::h4 || michael@0: tag == nsGkAtoms::h5 || michael@0: tag == nsGkAtoms::h6); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLCopyEncoder::PromoteRange(nsIDOMRange *inRange) michael@0: { michael@0: if (!inRange) return NS_ERROR_NULL_POINTER; michael@0: nsresult rv; michael@0: nsCOMPtr startNode, endNode, common; michael@0: int32_t startOffset, endOffset; michael@0: michael@0: rv = inRange->GetCommonAncestorContainer(getter_AddRefs(common)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = inRange->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = inRange->GetStartOffset(&startOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = inRange->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = inRange->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr opStartNode; michael@0: nsCOMPtr opEndNode; michael@0: int32_t opStartOffset, opEndOffset; michael@0: nsCOMPtr opRange; michael@0: michael@0: // examine range endpoints. michael@0: rv = GetPromotedPoint( kStart, startNode, startOffset, address_of(opStartNode), &opStartOffset, common); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = GetPromotedPoint( kEnd, endNode, endOffset, address_of(opEndNode), &opEndOffset, common); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // if both range endpoints are at the common ancestor, check for possible inclusion of ancestors michael@0: if ( (opStartNode == common) && (opEndNode == common) ) michael@0: { michael@0: rv = PromoteAncestorChain(address_of(opStartNode), &opStartOffset, &opEndOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: opEndNode = opStartNode; michael@0: } michael@0: michael@0: // set the range to the new values michael@0: rv = inRange->SetStart(opStartNode, opStartOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = inRange->SetEnd(opEndNode, opEndOffset); michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: // PromoteAncestorChain will promote a range represented by [{*ioNode,*ioStartOffset} , {*ioNode,*ioEndOffset}] michael@0: // The promotion is different from that found in getPromotedPoint: it will only promote one endpoint if it can michael@0: // promote the other. Thus, instead of having a startnode/endNode, there is just the one ioNode. michael@0: nsresult michael@0: nsHTMLCopyEncoder::PromoteAncestorChain(nsCOMPtr *ioNode, michael@0: int32_t *ioStartOffset, michael@0: int32_t *ioEndOffset) michael@0: { michael@0: if (!ioNode || !ioStartOffset || !ioEndOffset) return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsresult rv = NS_OK; michael@0: bool done = false; michael@0: michael@0: nsCOMPtr frontNode, endNode, parent; michael@0: int32_t frontOffset, endOffset; michael@0: michael@0: //save the editable state of the ioNode, so we don't promote an ancestor if it has different editable state michael@0: nsCOMPtr node = do_QueryInterface(*ioNode); michael@0: bool isEditable = node->IsEditable(); michael@0: michael@0: // loop for as long as we can promote both endpoints michael@0: while (!done) michael@0: { michael@0: rv = (*ioNode)->GetParentNode(getter_AddRefs(parent)); michael@0: if ((NS_FAILED(rv)) || !parent) michael@0: done = true; michael@0: else michael@0: { michael@0: // passing parent as last param to GetPromotedPoint() allows it to promote only one level michael@0: // up the hierarchy. michael@0: rv = GetPromotedPoint( kStart, *ioNode, *ioStartOffset, address_of(frontNode), &frontOffset, parent); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // then we make the same attempt with the endpoint michael@0: rv = GetPromotedPoint( kEnd, *ioNode, *ioEndOffset, address_of(endNode), &endOffset, parent); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr frontINode = do_QueryInterface(frontNode); michael@0: // if both endpoints were promoted one level and isEditable is the same as the original node, michael@0: // keep looping - otherwise we are done. michael@0: if ( (frontNode != parent) || (endNode != parent) || (frontINode->IsEditable() != isEditable) ) michael@0: done = true; michael@0: else michael@0: { michael@0: *ioNode = frontNode; michael@0: *ioStartOffset = frontOffset; michael@0: *ioEndOffset = endOffset; michael@0: } michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCopyEncoder::GetPromotedPoint(Endpoint aWhere, nsIDOMNode *aNode, int32_t aOffset, michael@0: nsCOMPtr *outNode, int32_t *outOffset, nsIDOMNode *common) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr node = aNode; michael@0: nsCOMPtr parent = aNode; michael@0: int32_t offset = aOffset; michael@0: bool bResetPromotion = false; michael@0: michael@0: // default values michael@0: *outNode = node; michael@0: *outOffset = offset; michael@0: michael@0: if (common == node) michael@0: return NS_OK; michael@0: michael@0: if (aWhere == kStart) michael@0: { michael@0: // some special casing for text nodes michael@0: nsCOMPtr t = do_QueryInterface(aNode); michael@0: if (IsTextNode(t)) michael@0: { michael@0: // if not at beginning of text node, we are done michael@0: if (offset > 0) michael@0: { michael@0: // unless everything before us in just whitespace. NOTE: we need a more michael@0: // general solution that truly detects all cases of non-significant michael@0: // whitesace with no false alarms. michael@0: nsCOMPtr nodeAsText = do_QueryInterface(aNode); michael@0: nsAutoString text; michael@0: nodeAsText->SubstringData(0, offset, text); michael@0: text.CompressWhitespace(); michael@0: if (!text.IsEmpty()) michael@0: return NS_OK; michael@0: bResetPromotion = true; michael@0: } michael@0: // else michael@0: rv = GetNodeLocation(aNode, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else michael@0: { michael@0: node = GetChildAt(parent,offset); michael@0: } michael@0: if (!node) node = parent; michael@0: michael@0: // finding the real start for this point. look up the tree for as long as we are the michael@0: // first node in the container, and as long as we haven't hit the body node. michael@0: if (!IsRoot(node) && (parent != common)) michael@0: { michael@0: rv = GetNodeLocation(node, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (offset == -1) return NS_OK; // we hit generated content; STOP michael@0: nsIParserService *parserService = nsContentUtils::GetParserService(); michael@0: if (!parserService) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: while ((IsFirstNode(node)) && (!IsRoot(parent)) && (parent != common)) michael@0: { michael@0: if (bResetPromotion) michael@0: { michael@0: nsCOMPtr content = do_QueryInterface(parent); michael@0: if (content) michael@0: { michael@0: bool isBlock = false; michael@0: parserService->IsBlock(parserService->HTMLAtomTagToId(content->Tag()), isBlock); michael@0: if (isBlock) michael@0: { michael@0: bResetPromotion = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: node = parent; michael@0: rv = GetNodeLocation(node, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (offset == -1) // we hit generated content; STOP michael@0: { michael@0: // back up a bit michael@0: parent = node; michael@0: offset = 0; michael@0: break; michael@0: } michael@0: } michael@0: if (bResetPromotion) michael@0: { michael@0: *outNode = aNode; michael@0: *outOffset = aOffset; michael@0: } michael@0: else michael@0: { michael@0: *outNode = parent; michael@0: *outOffset = offset; michael@0: } michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: if (aWhere == kEnd) michael@0: { michael@0: // some special casing for text nodes michael@0: nsCOMPtr n = do_QueryInterface(aNode); michael@0: if (IsTextNode(n)) michael@0: { michael@0: // if not at end of text node, we are done michael@0: uint32_t len = n->Length(); michael@0: if (offset < (int32_t)len) michael@0: { michael@0: // unless everything after us in just whitespace. NOTE: we need a more michael@0: // general solution that truly detects all cases of non-significant michael@0: // whitespace with no false alarms. michael@0: nsCOMPtr nodeAsText = do_QueryInterface(aNode); michael@0: nsAutoString text; michael@0: nodeAsText->SubstringData(offset, len-offset, text); michael@0: text.CompressWhitespace(); michael@0: if (!text.IsEmpty()) michael@0: return NS_OK; michael@0: bResetPromotion = true; michael@0: } michael@0: rv = GetNodeLocation(aNode, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else michael@0: { michael@0: if (offset) offset--; // we want node _before_ offset michael@0: node = GetChildAt(parent,offset); michael@0: } michael@0: if (!node) node = parent; michael@0: michael@0: // finding the real end for this point. look up the tree for as long as we are the michael@0: // last node in the container, and as long as we haven't hit the body node. michael@0: if (!IsRoot(node) && (parent != common)) michael@0: { michael@0: rv = GetNodeLocation(node, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (offset == -1) return NS_OK; // we hit generated content; STOP michael@0: nsIParserService *parserService = nsContentUtils::GetParserService(); michael@0: if (!parserService) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: while ((IsLastNode(node)) && (!IsRoot(parent)) && (parent != common)) michael@0: { michael@0: if (bResetPromotion) michael@0: { michael@0: nsCOMPtr content = do_QueryInterface(parent); michael@0: if (content) michael@0: { michael@0: bool isBlock = false; michael@0: parserService->IsBlock(parserService->HTMLAtomTagToId(content->Tag()), isBlock); michael@0: if (isBlock) michael@0: { michael@0: bResetPromotion = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: node = parent; michael@0: rv = GetNodeLocation(node, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (offset == -1) // we hit generated content; STOP michael@0: { michael@0: // back up a bit michael@0: parent = node; michael@0: offset = 0; michael@0: break; michael@0: } michael@0: } michael@0: if (bResetPromotion) michael@0: { michael@0: *outNode = aNode; michael@0: *outOffset = aOffset; michael@0: } michael@0: else michael@0: { michael@0: *outNode = parent; michael@0: offset++; // add one since this in an endpoint - want to be AFTER node. michael@0: *outOffset = offset; michael@0: } michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr michael@0: nsHTMLCopyEncoder::GetChildAt(nsIDOMNode *aParent, int32_t aOffset) michael@0: { michael@0: nsCOMPtr resultNode; michael@0: michael@0: if (!aParent) michael@0: return resultNode; michael@0: michael@0: nsCOMPtr content = do_QueryInterface(aParent); michael@0: NS_PRECONDITION(content, "null content in nsHTMLCopyEncoder::GetChildAt"); michael@0: michael@0: resultNode = do_QueryInterface(content->GetChildAt(aOffset)); michael@0: michael@0: return resultNode; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLCopyEncoder::IsMozBR(nsIDOMNode* aNode) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: return element && michael@0: element->IsHTML(nsGkAtoms::br) && michael@0: element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: NS_LITERAL_STRING("_moz"), eIgnoreCase); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCopyEncoder::GetNodeLocation(nsIDOMNode *inChild, michael@0: nsCOMPtr *outParent, michael@0: int32_t *outOffset) michael@0: { michael@0: NS_ASSERTION((inChild && outParent && outOffset), "bad args"); michael@0: nsresult result = NS_ERROR_NULL_POINTER; michael@0: if (inChild && outParent && outOffset) michael@0: { michael@0: result = inChild->GetParentNode(getter_AddRefs(*outParent)); michael@0: if ((NS_SUCCEEDED(result)) && (*outParent)) michael@0: { michael@0: nsCOMPtr content = do_QueryInterface(*outParent); michael@0: nsCOMPtr cChild = do_QueryInterface(inChild); michael@0: if (!cChild || !content) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: *outOffset = content->IndexOf(cChild); michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLCopyEncoder::IsRoot(nsIDOMNode* aNode) michael@0: { michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: if (content) michael@0: { michael@0: if (mIsTextWidget) michael@0: return (IsTag(content, nsGkAtoms::div)); michael@0: michael@0: return (IsTag(content, nsGkAtoms::body) || michael@0: IsTag(content, nsGkAtoms::td) || michael@0: IsTag(content, nsGkAtoms::th)); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLCopyEncoder::IsFirstNode(nsIDOMNode *aNode) michael@0: { michael@0: nsCOMPtr parent; michael@0: int32_t offset, j=0; michael@0: nsresult rv = GetNodeLocation(aNode, address_of(parent), &offset); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: NS_NOTREACHED("failure in IsFirstNode"); michael@0: return false; michael@0: } michael@0: if (offset == 0) // easy case, we are first dom child michael@0: return true; michael@0: if (!parent) michael@0: return true; michael@0: michael@0: // need to check if any nodes before us are really visible. michael@0: // Mike wrote something for me along these lines in nsSelectionController, michael@0: // but I don't think it's ready for use yet - revisit. michael@0: // HACK: for now, simply consider all whitespace text nodes to be michael@0: // invisible formatting nodes. michael@0: nsCOMPtr childList; michael@0: nsCOMPtr child; michael@0: michael@0: rv = parent->GetChildNodes(getter_AddRefs(childList)); michael@0: if (NS_FAILED(rv) || !childList) michael@0: { michael@0: NS_NOTREACHED("failure in IsFirstNode"); michael@0: return true; michael@0: } michael@0: while (j < offset) michael@0: { michael@0: childList->Item(j, getter_AddRefs(child)); michael@0: if (!IsEmptyTextContent(child)) michael@0: return false; michael@0: j++; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsHTMLCopyEncoder::IsLastNode(nsIDOMNode *aNode) michael@0: { michael@0: nsCOMPtr parent; michael@0: int32_t offset,j; michael@0: nsresult rv = GetNodeLocation(aNode, address_of(parent), &offset); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: NS_NOTREACHED("failure in IsLastNode"); michael@0: return false; michael@0: } michael@0: nsCOMPtr parentNode = do_QueryInterface(parent); michael@0: if (!parentNode) { michael@0: return true; michael@0: } michael@0: michael@0: uint32_t numChildren = parentNode->Length(); michael@0: if (offset+1 == (int32_t)numChildren) // easy case, we are last dom child michael@0: return true; michael@0: // need to check if any nodes after us are really visible. michael@0: // Mike wrote something for me along these lines in nsSelectionController, michael@0: // but I don't think it's ready for use yet - revisit. michael@0: // HACK: for now, simply consider all whitespace text nodes to be michael@0: // invisible formatting nodes. michael@0: j = (int32_t)numChildren-1; michael@0: nsCOMPtrchildList; michael@0: nsCOMPtr child; michael@0: rv = parent->GetChildNodes(getter_AddRefs(childList)); michael@0: if (NS_FAILED(rv) || !childList) michael@0: { michael@0: NS_NOTREACHED("failure in IsLastNode"); michael@0: return true; michael@0: } michael@0: while (j > offset) michael@0: { michael@0: childList->Item(j, getter_AddRefs(child)); michael@0: j--; michael@0: if (IsMozBR(child)) // we ignore trailing moz BRs. michael@0: continue; michael@0: if (!IsEmptyTextContent(child)) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLCopyEncoder::IsEmptyTextContent(nsIDOMNode* aNode) michael@0: { michael@0: nsCOMPtr cont = do_QueryInterface(aNode); michael@0: return cont && cont->TextIsOnlyWhitespace(); michael@0: } michael@0: michael@0: nsresult NS_NewHTMLCopyTextEncoder(nsIDocumentEncoder** aResult); // make mac compiler happy michael@0: michael@0: nsresult michael@0: NS_NewHTMLCopyTextEncoder(nsIDocumentEncoder** aResult) michael@0: { michael@0: *aResult = new nsHTMLCopyEncoder; michael@0: if (!*aResult) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(*aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t michael@0: nsHTMLCopyEncoder::GetImmediateContextCount(const nsTArray& aAncestorArray) michael@0: { michael@0: int32_t i = aAncestorArray.Length(), j = 0; michael@0: while (j < i) { michael@0: nsINode *node = aAncestorArray.ElementAt(j); michael@0: if (!node) { michael@0: break; michael@0: } michael@0: nsCOMPtr content(do_QueryInterface(node)); michael@0: if (!content || !content->IsHTML() || (content->Tag() != nsGkAtoms::tr && michael@0: content->Tag() != nsGkAtoms::thead && michael@0: content->Tag() != nsGkAtoms::tbody && michael@0: content->Tag() != nsGkAtoms::tfoot && michael@0: content->Tag() != nsGkAtoms::table)) { michael@0: break; michael@0: } michael@0: ++j; michael@0: } michael@0: return j; michael@0: } michael@0: