/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* * Object that can be used to serialize selections, ranges, or nodes * to strings in a gazillion different ways.
*/
class TextStreamer { public: /** * @param aStream Will be kept alive by the TextStreamer. * @param aUnicodeEncoder Needs to be non-nullptr.
*/
TextStreamer(nsIOutputStream& aStream, UniquePtr<Encoder> aUnicodeEncoder, bool aIsPlainText, nsAString& aOutputBuffer);
/** * String will be truncated if it is written to stream.
*/
nsresult FlushIfStringLongEnough();
/** * String will be truncated.
*/
nsresult ForceFlush();
nsresult TextStreamer::EncodeAndWrite() { if (mOutputBuffer.IsEmpty()) { return NS_OK;
}
uint8_t buffer[kEncoderBufferSizeInBytes]; auto src = Span(mOutputBuffer); auto bufferSpan = Span(buffer); // Reserve space for terminator auto dst = bufferSpan.To(bufferSpan.Length() - 1); for (;;) {
uint32_t result;
size_t read;
size_t written; if (mIsPlainText) {
std::tie(result, read, written) =
mUnicodeEncoder->EncodeFromUTF16WithoutReplacement(src, dst, false); if (result != kInputEmpty && result != kOutputFull) { // There's always room for one byte in the case of // an unmappable character, because otherwise // we'd have gotten `kOutputFull`.
dst[written++] = '?';
}
} else {
std::tie(result, read, written, std::ignore) =
mUnicodeEncoder->EncodeFromUTF16(src, dst, false);
}
src = src.From(read); // Sadly, we still have test cases that implement nsIOutputStream in JS, so // the buffer needs to be zero-terminated for XPConnect to do its thing. // See bug 170416.
bufferSpan[written] = 0;
uint32_t streamWritten;
nsresult rv = mStream->Write(reinterpret_cast<char*>(dst.Elements()),
written, &streamWritten); if (NS_FAILED(rv)) { return rv;
} if (result == kInputEmpty) { return NS_OK;
}
}
}
/** * The scope may be limited to either a selection, range, or node.
*/ class EncodingScope { public: /** * @return true, iff the scope is limited to a selection, range or node.
*/ bool IsLimited() const;
// The first node is the range's boundary node, the following ones the // ancestors.
InclusiveAncestors mInclusiveAncestorsOfStart; // The first offset represents where at the boundary node the range starts. // Each other offset is the index of the child relative to its parent.
InclusiveAncestorsOffsets mInclusiveAncestorsOffsetsOfStart;
// The first node is the range's boundary node, the following one the // ancestors.
InclusiveAncestors mInclusiveAncestorsOfEnd; // The first offset represents where at the boundary node the range ends. // Each other offset is the index of the child relative to its parent.
InclusiveAncestorsOffsets mInclusiveAncestorsOffsetsOfEnd;
};
/** * @param aMaxLength As described at * `nsIDocumentEncodder.encodeToStringWithMaxLength`.
*/
nsresult SerializeDependingOnScope(uint32_t aMaxLength);
nsresult SerializeSelection();
nsresult SerializeNode();
/** * @param aMaxLength As described at * `nsIDocumentEncodder.encodeToStringWithMaxLength`.
*/
nsresult SerializeWholeDocument(uint32_t aMaxLength);
/** * @param aFlags multiple of the flags defined in nsIDocumentEncoder.idl.o
*/ staticbool IsInvisibleNodeAndShouldBeSkipped(const nsINode& aNode, const uint32_t aFlags) { if (aFlags & SkipInvisibleContent) { // Treat the visibility of the ShadowRoot as if it were // the host content. // // FIXME(emilio): I suspect instead of this a bunch of the GetParent() // calls here should be doing GetFlattenedTreeParent, then this condition // should be unreachable... const nsINode* node{&aNode}; if (const ShadowRoot* shadowRoot = ShadowRoot::FromNode(node)) {
node = shadowRoot->GetHost();
}
if (node->IsContent()) {
nsIFrame* frame = node->AsContent()->GetPrimaryFrame(); if (!frame) { if (node->IsElement() && node->AsElement()->IsDisplayContents()) { returnfalse;
} if (node->IsText()) { // We have already checked that our parent is visible. // // FIXME(emilio): Text not assigned to a <slot> in Shadow DOM should // probably return false... returnfalse;
} if (node->IsHTMLElement(nsGkAtoms::rp)) { // Ruby parentheses are part of ruby structure, hence // shouldn't be stripped out even if it is not displayed. returnfalse;
} returntrue;
} if (node->IsText() &&
(!frame->StyleVisibility()->IsVisible() ||
frame->IsHiddenByContentVisibilityOnAnyAncestor())) { returntrue;
}
}
} returnfalse;
}
nsString mMimeType; const Encoding* mEncoding; // Multiple of the flags defined in nsIDocumentEncoder.idl.
uint32_t mFlags;
uint32_t mWrapColumn; // Whether the serializer cares about being notified to scan elements to // keep track of whether they are preformatted. This stores the out // argument of nsIContentSerializer::Init(). bool mNeedsPreformatScanning; bool mIsCopying; // Set to true only while copying
RefPtr<StringBuffer> mCachedBuffer;
class NodeSerializer { public: /** * @param aFlags multiple of the flags defined in nsIDocumentEncoder.idl.
*/
NodeSerializer(constbool& aNeedsPreformatScanning, const nsCOMPtr<nsIContentSerializer>& aSerializer, const uint32_t& aFlags, const nsCOMPtr<nsIDocumentEncoderNodeFixup>& aNodeFixup,
Maybe<TextStreamer>& aTextStreamer)
: mNeedsPreformatScanning{aNeedsPreformatScanning},
mSerializer{aSerializer},
mFlags{aFlags},
mNodeFixup{aNodeFixup},
mTextStreamer{aTextStreamer} {}
// Used when context has already been serialized for // table cell selections (where parent is <tr>) bool mDisableContextSerialize;
AutoTArray<AutoTArray<nsINode*, 8>, 8> mRangeContexts;
// Bug 236546: newlines not added when copying table cells into clipboard // Each selected cell shows up as a range containing a row with a single // cell get the row, compare it to previous row and emit </tr><tr> as // needed Bug 137450: Problem copying/pasting a table from a web page to // Excel. Each separate block of <tr></tr> produced above will be wrapped // by the immediate context. This assumes that you can't select cells that // are multiple selections from two tables simultaneously.
node = ShadowDOMSelectionHelpers::GetStartContainer(
range, mFlags & nsIDocumentEncoder::AllowCrossShadowBoundary);
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); if (node != prevNode) { if (prevNode) {
rv = mNodeSerializer.SerializeNodeEnd(*prevNode);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIContent> content = nsIContent::FromNodeOrNull(node); if (content && content->IsHTMLElement(nsGkAtoms::tr) &&
!ParentIsTR(content)) { if (!prevNode) { // Went from a non-<tr> to a <tr>
mRangeSerializer.mCommonInclusiveAncestors.Clear();
nsContentUtils::GetInclusiveAncestors(
node->GetParentNode(),
mRangeSerializer.mCommonInclusiveAncestors);
rv = mRangeContextSerializer.SerializeRangeContextStart(
mRangeSerializer.mCommonInclusiveAncestors);
NS_ENSURE_SUCCESS(rv, rv); // Don't let SerializeRangeToString serialize the context again
mRangeContextSerializer.mDisableContextSerialize = true;
}
rv = mNodeSerializer.SerializeNodeStart(*node, 0, -1);
NS_ENSURE_SUCCESS(rv, rv);
prevNode = node;
} elseif (prevNode) { // Went from a <tr> to a non-<tr>
mRangeContextSerializer.mDisableContextSerialize = false;
// `mCommonInclusiveAncestors` is used in `EncodeToStringWithContext` // too. Update it here to mimic the old behavior.
mRangeSerializer.mCommonInclusiveAncestors.Clear();
nsContentUtils::GetInclusiveAncestors(
prevNode->GetParentNode(),
mRangeSerializer.mCommonInclusiveAncestors);
// `mCommonInclusiveAncestors` is used in `EncodeToStringWithContext` // too. Update it here to mimic the old behavior.
mRangeSerializer.mCommonInclusiveAncestors.Clear();
nsContentUtils::GetInclusiveAncestors(
prevNode->GetParentNode(), mRangeSerializer.mCommonInclusiveAncestors);
/** * @return The fixup node, if available, otherwise the original node. The * former is kept alive by this object.
*/
nsINode& GetFixupNodeFallBackToOriginalNode() const { return mFixupNode ? *mFixupNode : mOriginalNode;
}
if (shadowRoot) {
MOZ_ASSERT(StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()); // Serialize the ShadowRoot first when the entire node needs to be // serialized.
SerializeToStringRecursive(shadowRoot, aSerializeRoot, aMaxLength);
}
for (nsINode* child = node->GetFirstChildOfTemplateOrNode(); child;
child = child->GetNextSibling()) { if (shadowRoot &&
(!child->IsContent() || !child->AsContent()->GetAssignedSlot())) { // Since this node is a shadow host, we skip the children that are not // slotted because they aren't visible. continue;
}
rv = SerializeToStringRecursive(child, SerializeRoot::eYes, aMaxLength);
NS_ENSURE_SUCCESS(rv, rv);
}
nsINode* node = aNode->GetFirstChildOfTemplateOrNode(); while (node) {
nsINode* current = node;
rv = SerializeNodeStart(*current, 0, -1, current);
NS_ENSURE_SUCCESS(rv, rv);
node = current->GetFirstChildOfTemplateOrNode(); while (!node && current && current != aNode) {
rv = SerializeNodeEnd(*current);
NS_ENSURE_SUCCESS(rv, rv); // Check if we have siblings.
node = current->GetNextSibling(); if (!node) { // Perhaps parent node has siblings.
current = current->GetParentNode();
// Handle template element. If the parent is a template's content, // then adjust the parent to be the template element. if (current && current != aNode && current->IsDocumentFragment()) {
nsIContent* host = current->AsDocumentFragment()->GetHost(); if (host && host->IsHTMLElement(nsGkAtoms::_template)) {
current = host;
}
}
}
}
}
if (startAndEndContent.mStart != content &&
startAndEndContent.mEnd != content) { // node is completely contained in range. Serialize the whole subtree // rooted by this node.
rv = mNodeSerializer.SerializeToStringRecursive(
aNode, NodeSerializer::SerializeRoot::eYes);
NS_ENSURE_SUCCESS(rv, rv);
} else {
rv = SerializeNodePartiallyContainedInRange(
*aNode, *content, startAndEndContent, *aRange, aDepth); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
} return NS_OK;
}
nsresult
nsDocumentEncoder::RangeSerializer::SerializeNodePartiallyContainedInRange(
nsINode& aNode, nsIContent& aContent, const StartAndEndContent& aStartAndEndContent, const nsRange& aRange, const int32_t aDepth) { // due to implementation it is impossible for text node to be both start and // end of range. We would have handled that case without getting here. // XXXsmaug What does this all mean? if (IsTextNode(&aNode)) {
nsresult rv =
SerializeTextNode(aNode, aContent, aStartAndEndContent, aRange);
NS_ENSURE_SUCCESS(rv, rv);
} else { if (&aNode != mClosestCommonInclusiveAncestorOfRange) { if (mRangeContextSerializer.mRangeNodeContext.IncludeInContext(aNode)) { // halt the incrementing of mContextInfoDepth. This // is so paste client will include this node in paste.
mHaltRangeHint = true;
} if ((aStartAndEndContent.mStart == &aContent) && !mHaltRangeHint) {
++mContextInfoDepth.mStart;
} if ((aStartAndEndContent.mEnd == &aContent) && !mHaltRangeHint) {
++mContextInfoDepth.mEnd;
}
// serialize the start of this node
nsresult rv = mNodeSerializer.SerializeNodeStart(aNode, 0, -1);
NS_ENSURE_SUCCESS(rv, rv);
}
constauto& inclusiveAncestorsOffsetsOfStart =
mRangeBoundariesInclusiveAncestorsAndOffsets
.mInclusiveAncestorsOffsetsOfStart; constauto& inclusiveAncestorsOffsetsOfEnd =
mRangeBoundariesInclusiveAncestorsAndOffsets
.mInclusiveAncestorsOffsetsOfEnd; // do some calculations that will tell us which children of this // node are in the range.
Maybe<uint32_t> startOffset = Some(0);
Maybe<uint32_t> endOffset; if (aStartAndEndContent.mStart == &aContent && mStartRootIndex >= aDepth) {
startOffset = inclusiveAncestorsOffsetsOfStart[mStartRootIndex - aDepth];
} if (aStartAndEndContent.mEnd == &aContent && mEndRootIndex >= aDepth) {
endOffset = inclusiveAncestorsOffsetsOfEnd[mEndRootIndex - aDepth];
} // generated aContent will cause offset values of Nothing to be returned. if (startOffset.isNothing()) {
startOffset = Some(0);
} if (endOffset.isNothing()) {
endOffset = Some(aContent.GetChildCount());
} else { // if we are at the "tip" of the selection, endOffset is fine. // otherwise, we need to add one. This is because of the semantics // of the offset list created by GetInclusiveAncestorsAndOffsets(). The // intermediate points on the list use the endOffset of the // location of the ancestor, rather than just past it. So we need // to add one here in order to include it in the children we serialize. const nsINode* endContainer = ShadowDOMSelectionHelpers::GetEndContainer(
&aRange, mAllowCrossShadowBoundary); if (&aNode != endContainer) {
MOZ_ASSERT(*endOffset != UINT32_MAX);
endOffset.ref()++;
}
}
// serialize the end of this node if (&aNode != mClosestCommonInclusiveAncestorOfRange) {
nsresult rv = mNodeSerializer.SerializeNodeEnd(aNode);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
nsresult nsDocumentEncoder::RangeSerializer::SerializeChildrenOfContent(
nsIContent& aContent, uint32_t aStartOffset, uint32_t aEndOffset, const nsRange* aRange, int32_t aDepth) {
ShadowRoot* shadowRoot = ShadowDOMSelectionHelpers::GetShadowRoot(
&aContent, mAllowCrossShadowBoundary); if (shadowRoot) { // Serialize the ShadowRoot first when the entire node needs to be // serialized.
SerializeRangeNodes(aRange, shadowRoot, aDepth + 1);
}
if (!aEndOffset) { return NS_OK;
} // serialize the children of this node that are in the range
nsIContent* childAsNode = aContent.GetFirstChild();
uint32_t j = 0;
for (; childAsNode && j < aEndOffset; ++j) { if (shadowRoot && !childAsNode->GetAssignedSlot()) {
childAsNode = childAsNode->GetNextSibling(); // Since this node is a shadow host, we skip the children that are not // slotted because they aren't visible. continue;
}
nsresult rv{NS_OK}; if ((j == aStartOffset) || (j == aEndOffset - 1)) {
rv = SerializeRangeNodes(aRange, childAsNode, aDepth + 1);
} else {
rv = mNodeSerializer.SerializeToStringRecursive(
childAsNode, NodeSerializer::SerializeRoot::eYes);
}
int32_t i = aAncestorArray.Length(), j;
nsresult rv = NS_OK;
// currently only for table-related elements; see Bug 137450
j = mRangeNodeContext.GetImmediateContextCount(aAncestorArray);
while (i > 0) {
nsINode* node = aAncestorArray.ElementAt(--i); if (!node) break;
// Either a general inclusion or as immediate context if (mRangeNodeContext.IncludeInContext(*node) || i < j) {
rv = mNodeSerializer.SerializeNodeStart(*node, 0, -1);
serializedContext->AppendElement(node); if (NS_FAILED(rv)) break;
}
}
return rv;
}
nsresult nsDocumentEncoder::RangeContextSerializer::SerializeRangeContextEnd() { if (mDisableContextSerialize) { return NS_OK;
}
MOZ_RELEASE_ASSERT(!mRangeContexts.IsEmpty(), "Tried to end context without starting one.");
AutoTArray<nsINode*, 8>& serializedContext = mRangeContexts.LastElement();
// Check that the parent is visible if we don't a frame. // IsInvisibleNodeAndShouldBeSkipped() will do it when there's a frame.
nsCOMPtr<nsIContent> content = nsIContent::FromNode(aNode); if (content && !content->GetPrimaryFrame()) {
nsIContent* parent = content->GetParent(); return !parent || IsInvisibleNodeAndShouldBeSkipped(*parent, mFlags);
}
// Consider a case where the boundary of the selection is ShadowRoot (ie, the // first child of ShadowRoot is selected, so ShadowRoot is the container hence // the boundary), allowing GetClosestCommonInclusiveAncestor to cross the // boundary can return the host element as the container. // SerializeRangeContextStart doesn't support this case.
mClosestCommonInclusiveAncestorOfRange =
aRange->GetClosestCommonInclusiveAncestor(
mAllowCrossShadowBoundary ? AllowRangeCrossShadowBoundary::Yes
: AllowRangeCrossShadowBoundary::No);
if (!mClosestCommonInclusiveAncestorOfRange) { return NS_OK;
}
// We have to be careful how we set aOutputString, because we don't // want it to end up sharing mCachedBuffer if we plan to reuse it. bool setOutput = false;
MOZ_ASSERT(!mCachedBuffer); // Try to cache the buffer. if (StringBuffer* outputBuffer = output.GetStringBuffer()) { if (outputBuffer->StorageSize() == kStringBufferSizeInBytes &&
!outputBuffer->IsReadonly()) {
mCachedBuffer = outputBuffer;
} elseif (NS_SUCCEEDED(rv)) {
aOutputString.Assign(outputBuffer, output.Length());
setOutput = true;
}
}
if (!setOutput && NS_SUCCEEDED(rv)) {
aOutputString.Append(output.get(), output.Length());
}
return rv;
}
NS_IMETHODIMP
nsDocumentEncoder::EncodeToStream(nsIOutputStream* aStream) {
MOZ_ASSERT(mRangeContextSerializer.mRangeContexts.IsEmpty(), "Re-entrant call to nsDocumentEncoder."); auto rangeContextGuard =
MakeScopeExit([&] { mRangeContextSerializer.mRangeContexts.Clear(); });
NS_ENSURE_ARG_POINTER(aStream);
class nsHTMLCopyEncoder : public nsDocumentEncoder { private: class RangeNodeContext final : public nsDocumentEncoder::RangeNodeContext { bool IncludeInContext(nsINode& aNode) const final;
// Hack, hack! Traditionally, the caller passes text/plain, which is // treated as "guess text/html or text/plain" in this context. (It has a // different meaning in other contexts. Sigh.) From now on, "text/plain" // means forcing text/plain instead of guessing. if (aMimeType.EqualsLiteral("text/plain")) {
mMimeType.AssignLiteral("text/plain");
} else {
mMimeType.AssignLiteral("text/html");
}
// Make all links absolute when copying // (see related bugs #57296, #41924, #58646, #32768)
mFlags = aFlags | OutputAbsoluteLinks;
if (!mDocument->IsScriptEnabled()) mFlags |= OutputNoScriptContent;
return NS_OK;
}
NS_IMETHODIMP
nsHTMLCopyEncoder::SetSelection(Selection* aSelection) { // check for text widgets: we need to recognize these so that // we don't tweak the selection to be outside of the magic // div that ender-lite text widgets are embedded in.
// if selection is uninitialized return if (!rangeCount) { return NS_ERROR_FAILURE;
}
// we'll just use the common parent of the first range. Implicit assumption // here that multi-range selections are table cell selections, in which case // the common parent is somewhere in the table and we don't really care where. // // FIXME(emilio, bug 1455894): This assumption is already wrong, and will // probably be more wrong in a Shadow DOM world... // // We should be able to write this as "Find the common ancestor of the // selection, then go through the flattened tree and serialize the selected // nodes", effectively serializing the composed tree.
RefPtr<nsRange> range = aSelection->GetRangeAt(0);
nsINode* commonParent = range->GetClosestCommonInclusiveAncestor();
for (nsCOMPtr<nsIContent> selContent(
nsIContent::FromNodeOrNull(commonParent));
selContent; selContent = selContent->GetParent()) { // checking for selection inside a plaintext form widget if (selContent->IsAnyOfHTMLElements(nsGkAtoms::input,
nsGkAtoms::textarea)) {
mIsTextWidget = true; break;
}
}
// normalize selection if we are not in a widget if (mIsTextWidget) {
mEncodingScope.mSelection = aSelection;
mMimeType.AssignLiteral("text/plain"); return NS_OK;
}
// XXX We should try to get rid of the Selection object here. // XXX bug 1245883
// also consider ourselves in a text widget if we can't find an html document if (!(mDocument && mDocument->IsHTMLDocument())) {
mIsTextWidget = true;
mEncodingScope.mSelection = aSelection; // mMimeType is set to text/plain when encoding starts. return NS_OK;
}
// there's no Clone() for selection! fix... // nsresult rv = aSelection->Clone(getter_AddRefs(mSelection); // NS_ENSURE_SUCCESS(rv, rv);
mEncodingScope.mSelection = new Selection(SelectionType::eNormal, nullptr);
// loop thru the ranges in the selection for (const uint32_t rangeIdx : IntegerRange(rangeCount)) {
MOZ_ASSERT(aSelection->RangeCount() == rangeCount);
range = aSelection->GetRangeAt(rangeIdx);
NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
RefPtr<nsRange> myRange = range->CloneRange();
MOZ_ASSERT(myRange);
// adjust range to include any ancestors who's children are entirely // selected
nsresult rv = PromoteRange(myRange);
NS_ENSURE_SUCCESS(rv, rv);
// do not encode any context info or range hints if we are in a text widget. if (mIsTextWidget) return NS_OK;
// now encode common ancestors into aContextString. Note that the common // ancestors will be for the last range in the selection in the case of // multirange selections. encoding ancestors every range in a multirange // selection in a way that could be understood by the paste code would be a // lot more work to do. As a practical matter, selections are single range, // and the ones that aren't are table cell selections where all the cells are // in the same table.
// leaf of ancestors might be text node. If so discard it.
int32_t count = mRangeSerializer.mCommonInclusiveAncestors.Length();
int32_t i;
nsCOMPtr<nsINode> node; if (count > 0) {
node = mRangeSerializer.mCommonInclusiveAncestors.ElementAt(0);
}
if (node && IsTextNode(node)) {
mRangeSerializer.mCommonInclusiveAncestors.RemoveElementAt(0); if (mRangeSerializer.mContextInfoDepth.mStart) {
--mRangeSerializer.mContextInfoDepth.mStart;
} if (mRangeSerializer.mContextInfoDepth.mEnd) {
--mRangeSerializer.mContextInfoDepth.mEnd;
}
count--;
}
i = count; while (i > 0) {
node = mRangeSerializer.mCommonInclusiveAncestors.ElementAt(--i);
rv = mNodeSerializer.SerializeNodeStart(*node, 0, -1);
NS_ENSURE_SUCCESS(rv, rv);
} // i = 0; guaranteed by above while (i < count) {
node = mRangeSerializer.mCommonInclusiveAncestors.ElementAt(i++);
rv = mNodeSerializer.SerializeNodeEnd(*node);
NS_ENSURE_SUCCESS(rv, rv);
}
mSerializer->Finish();
// encode range info : the start and end depth of the selection, where the // depth is distance down in the parent hierarchy. Later we will need to add // leading/trailing whitespace info to this.
nsAutoString infoString;
infoString.AppendInt(mRangeSerializer.mContextInfoDepth.mStart);
infoString.Append(char16_t(','));
infoString.AppendInt(mRangeSerializer.mContextInfoDepth.mEnd);
aInfoString = infoString;
// If it's an inline editing host, we should not treat it gives a context to // avoid to duplicate its style. if (content->IsEditingHost()) { returnfalse;
}
// if both range endpoints are at the common ancestor, check for possible // inclusion of ancestors if (opStartNode == common && opEndNode == common) {
rv = PromoteAncestorChain(address_of(opStartNode), &opStartOffset,
&opEndOffset);
NS_ENSURE_SUCCESS(rv, rv);
opEndNode = opStartNode;
}
// set the range to the new values
ErrorResult err; constbool allowRangeCrossShadowBoundary =
mFlags & nsIDocumentEncoder::AllowCrossShadowBoundary;
inRange->SetStart(*opStartNode, static_cast<uint32_t>(opStartOffset), err,
allowRangeCrossShadowBoundary
? AllowRangeCrossShadowBoundary::Yes
: AllowRangeCrossShadowBoundary::No); if (NS_WARN_IF(err.Failed())) { return err.StealNSResult();
}
inRange->SetEnd(*opEndNode, static_cast<uint32_t>(opEndOffset), err,
allowRangeCrossShadowBoundary
? AllowRangeCrossShadowBoundary::Yes
: AllowRangeCrossShadowBoundary::No); if (NS_WARN_IF(err.Failed())) { return err.StealNSResult();
} return NS_OK;
}
// PromoteAncestorChain will promote a range represented by // [{*ioNode,*ioStartOffset} , {*ioNode,*ioEndOffset}] The promotion is // different from that found in getPromotedPoint: it will only promote one // endpoint if it can promote the other. Thus, instead of having a // startnode/endNode, there is just the one ioNode.
nsresult nsHTMLCopyEncoder::PromoteAncestorChain(nsCOMPtr<nsINode>* ioNode,
int32_t* ioStartOffset,
int32_t* ioEndOffset) { if (!ioNode || !ioStartOffset || !ioEndOffset) return NS_ERROR_NULL_POINTER;
// save the editable state of the ioNode, so we don't promote an ancestor if // it has different editable state
nsCOMPtr<nsINode> node = *ioNode; bool isEditable = node->IsEditable();
// loop for as long as we can promote both endpoints while (!done) {
node = *ioNode;
parent = node->GetParentNode(); if (!parent) {
done = true;
} else { // passing parent as last param to GetPromotedPoint() allows it to promote // only one level up the hierarchy.
rv = GetPromotedPoint(kStart, *ioNode, *ioStartOffset,
address_of(frontNode), &frontOffset, parent);
NS_ENSURE_SUCCESS(rv, rv); // then we make the same attempt with the endpoint
rv = GetPromotedPoint(kEnd, *ioNode, *ioEndOffset, address_of(endNode),
&endOffset, parent);
NS_ENSURE_SUCCESS(rv, rv);
// if both endpoints were promoted one level and isEditable is the same as // the original node, keep looping - otherwise we are done. if ((frontNode != parent) || (endNode != parent) ||
(frontNode->IsEditable() != isEditable))
done = true; else {
*ioNode = frontNode;
*ioStartOffset = frontOffset;
*ioEndOffset = endOffset;
}
}
} return rv;
}
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.36 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.