From 232bc61244f2a432c502cadb0f92abbaa684b7a1 Mon Sep 17 00:00:00 2001 From: Erik Nordin Date: Thu, 23 Apr 2020 16:41:49 +0000 Subject: [PATCH] Bug 1620289 - Part 1 Clone Selection Ranges For Printing r=jwatt - Clones selection ranges to the static document for printing - Static docs can remove selections without referring to original doc Differential Revision: https://phabricator.services.mozilla.com/D71110 --- layout/printing/nsPrintJob.cpp | 84 +++++++++--------------- layout/printing/nsPrintObject.cpp | 105 ++++++++++++++++++++++++++++++ xpcom/ds/StaticAtoms.py | 1 + 3 files changed, 135 insertions(+), 55 deletions(-) diff --git a/layout/printing/nsPrintJob.cpp b/layout/printing/nsPrintJob.cpp index 141887437ac4..aa26076a1675 100644 --- a/layout/printing/nsPrintJob.cpp +++ b/layout/printing/nsPrintJob.cpp @@ -142,7 +142,7 @@ static const char* gPrintRangeStr[] = { // This processes the selection on aOrigDoc and creates an inverted selection on // aDoc, which it then deletes. If the start or end of the inverted selection // ranges occur in text nodes then an ellipsis is added. -static nsresult DeleteUnselectedNodes(Document* aOrigDoc, Document* aDoc); +static nsresult DeleteNonSelectedNodes(Document& aDoc); #ifdef EXTENDED_DEBUG_PRINTING // Forward Declarations @@ -2053,8 +2053,7 @@ nsresult nsPrintJob::ReflowPrintObject(const UniquePtr& aPO) { int16_t printRangeType = nsIPrintSettings::kRangeAllPages; printData->mPrintSettings->GetPrintRange(&printRangeType); if (printRangeType == nsIPrintSettings::kRangeSelection) { - DeleteUnselectedNodes(aPO->mDocument->GetOriginalDocument(), - aPO->mDocument); + DeleteNonSelectedNodes(*aPO->mDocument); } bool doReturn = false; @@ -2202,61 +2201,39 @@ bool nsPrintJob::PrintDocContent(const UniquePtr& aPO, return false; } -static nsINode* GetCorrespondingNodeInDocument(const nsINode* aNode, - Document* aDoc) { - MOZ_ASSERT(aNode); - MOZ_ASSERT(aDoc); - - // Selections in anonymous subtrees aren't supported. - if (aNode->IsInAnonymousSubtree()) { - return nullptr; - } - - nsTArray indexArray; - const nsINode* child = aNode; - while (const nsINode* parent = child->GetParentNode()) { - int32_t index = parent->ComputeIndexOf(child); - MOZ_ASSERT(index >= 0); - indexArray.AppendElement(index); - child = parent; - } - MOZ_ASSERT(child->IsDocument()); - - nsINode* correspondingNode = aDoc; - for (int32_t i = indexArray.Length() - 1; i >= 0; --i) { - correspondingNode = correspondingNode->GetChildAt_Deprecated(indexArray[i]); - NS_ENSURE_TRUE(correspondingNode, nullptr); - } - - return correspondingNode; -} - static NS_NAMED_LITERAL_STRING(kEllipsis, u"\x2026"); -MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DeleteUnselectedNodes( - Document* aOrigDoc, Document* aDoc) { - PresShell* origPresShell = aOrigDoc->GetPresShell(); - PresShell* presShell = aDoc->GetPresShell(); - NS_ENSURE_STATE(origPresShell && presShell); +/** + * Builds the complement set of ranges and adds those to the selection. + * Deletes all of the nodes contained in the complement set of ranges + * leaving behind only nodes that were originally selected. + * Adds ellipses to a selected node's text if text is truncated by a range. + * This is used to implement the "Print Selection Only" user interface option. + */ +MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DeleteNonSelectedNodes( + Document& aDoc) { + MOZ_ASSERT(aDoc.IsStaticDocument()); + const auto* printRanges = static_cast>*>( + aDoc.GetProperty(nsGkAtoms::printselectionranges)); + if (!printRanges) { + return NS_OK; + } - RefPtr origSelection = - origPresShell->GetCurrentSelection(SelectionType::eNormal); + PresShell* presShell = aDoc.GetPresShell(); + NS_ENSURE_STATE(presShell); RefPtr selection = presShell->GetCurrentSelection(SelectionType::eNormal); - NS_ENSURE_STATE(origSelection && selection); + NS_ENSURE_STATE(selection); - nsINode* bodyNode = aDoc->GetBodyElement(); + MOZ_ASSERT(!selection->RangeCount()); + nsINode* bodyNode = aDoc.GetBodyElement(); nsINode* startNode = bodyNode; uint32_t startOffset = 0; uint32_t ellipsisOffset = 0; - int32_t rangeCount = origSelection->RangeCount(); - for (int32_t i = 0; i < rangeCount; ++i) { - nsRange* origRange = origSelection->GetRangeAt(i); - + for (nsRange* origRange : *printRanges) { // New end is start of original range. - nsINode* endNode = - GetCorrespondingNodeInDocument(origRange->GetStartContainer(), aDoc); + nsINode* endNode = origRange->GetStartContainer(); // If we're no longer in the same text node reset the ellipsis offset. if (endNode != startNode) { @@ -2267,13 +2244,12 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DeleteUnselectedNodes( // Create the range that we want to remove. Note that if startNode or // endNode are null nsRange::Create() will fail and we won't remove // that section. - RefPtr range = nsRange::Create(startNode, startOffset, endNode, - endOffset, IgnoreErrors()); + RefPtr unselectedRange = nsRange::Create( + startNode, startOffset, endNode, endOffset, IgnoreErrors()); - if (range && !range->Collapsed()) { - selection->AddRangeAndSelectFramesAndNotifyListeners(*range, + if (unselectedRange && !unselectedRange->Collapsed()) { + selection->AddRangeAndSelectFramesAndNotifyListeners(*unselectedRange, IgnoreErrors()); - // Unless we've already added an ellipsis at the start, if we ended mid // text node then add ellipsis. Text* text = endNode->GetAsText(); @@ -2284,8 +2260,7 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DeleteUnselectedNodes( } // Next new start is end of original range. - startNode = - GetCorrespondingNodeInDocument(origRange->GetEndContainer(), aDoc); + startNode = origRange->GetEndContainer(); // If we're no longer in the same text node reset the ellipsis offset. if (startNode != endNode) { @@ -2310,7 +2285,6 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DeleteUnselectedNodes( selection->AddRangeAndSelectFramesAndNotifyListeners(*lastRange, IgnoreErrors()); } - selection->DeleteFromDocument(IgnoreErrors()); return NS_OK; } diff --git a/layout/printing/nsPrintObject.cpp b/layout/printing/nsPrintObject.cpp index c99ac054a10c..5e11da1a1da5 100644 --- a/layout/printing/nsPrintObject.cpp +++ b/layout/printing/nsPrintObject.cpp @@ -52,11 +52,115 @@ nsPrintObject::~nsPrintObject() { nsDocShell::Cast(mDocShell)->Destroy(); bc->Detach(); } + if (mDocument) { + mDocument->RemoveProperty(nsGkAtoms::printselectionranges); + } mDocShell = nullptr; mTreeOwner = nullptr; // mTreeOwner must be released after mDocShell; } //------------------------------------------------------------------ + +/** + * Retrieves the node in a static-clone document that corresponds to aOrigNOde, + * which is a node in the original document from which aStaticClone was cloned. + */ +static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode, + Document& aStaticClone) { + MOZ_ASSERT(aOrigNode); + + // Selections in anonymous subtrees aren't supported. + if (aOrigNode->IsInAnonymousSubtree() || aOrigNode->IsInShadowTree()) { + return nullptr; + } + + nsTArray indexArray; + const nsINode* child = aOrigNode; + while (const nsINode* parent = child->GetParentNode()) { + int32_t index = parent->ComputeIndexOf(child); + MOZ_ASSERT(index >= 0); + indexArray.AppendElement(index); + child = parent; + } + MOZ_ASSERT(child->IsDocument()); + + nsINode* correspondingNode = &aStaticClone; + for (int32_t i : Reversed(indexArray)) { + correspondingNode = correspondingNode->GetChildAt_Deprecated(i); + NS_ENSURE_TRUE(correspondingNode, nullptr); + } + + return correspondingNode; +} + +/** + * Caches the selection ranges from the source document onto the static clone in + * case the "Print Selection Only" functionality is invoked. + * + * Note that we cannot use the selection obtained from + * Document::GetOriginalDocument() since that selection may have mutated after + * the print was invoked. + * + * Note also that because nsRange objects point into a specific document's + * nodes, we cannot reuse an array of nsRange objects across multiple static + * clone documents. For that reason we cache a new array of ranges on each + * static clone that we create. + */ +static void CachePrintSelectionRanges(const Document& aSourceDoc, + Document& aStaticClone) { + MOZ_ASSERT(aStaticClone.IsStaticDocument()); + MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges)); + + const Selection* origSelection = nullptr; + const nsTArray>* origRanges = nullptr; + bool sourceDocIsStatic = aSourceDoc.IsStaticDocument(); + + if (sourceDocIsStatic) { + origRanges = static_cast>*>( + aSourceDoc.GetProperty(nsGkAtoms::printselectionranges)); + } else if (PresShell* shell = aSourceDoc.GetPresShell()) { + origSelection = shell->GetCurrentSelection(SelectionType::eNormal); + } + + if (!origSelection && !origRanges) { + return; + } + + size_t rangeCount = + sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount(); + auto* printRanges = new nsTArray>(rangeCount); + + for (size_t i = 0; i < rangeCount; ++i) { + nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get() + : origSelection->GetRangeAt(i); + nsINode* startContainer = range->GetStartContainer(); + nsINode* endContainer = range->GetEndContainer(); + + if (!startContainer || !endContainer) { + continue; + } + + nsINode* startNode = + GetCorrespondingNodeInDocument(startContainer, aStaticClone); + nsINode* endNode = + GetCorrespondingNodeInDocument(endContainer, aStaticClone); + + if (!startNode || !endNode) { + continue; + } + + RefPtr clonedRange = + nsRange::Create(startNode, range->StartOffset(), endNode, + range->EndOffset(), IgnoreErrors()); + if (clonedRange && !clonedRange->Collapsed()) { + printRanges->AppendElement(std::move(clonedRange)); + } + } + + aStaticClone.SetProperty(nsGkAtoms::printselectionranges, printRanges, + nsINode::DeleteProperty>>); +} + nsresult nsPrintObject::InitAsRootObject(nsIDocShell* aDocShell, Document* aDoc, bool aForPrintPreview) { NS_ENSURE_STATE(aDocShell); @@ -97,6 +201,7 @@ nsresult nsPrintObject::InitAsRootObject(nsIDocShell* aDocShell, Document* aDoc, mDocument = aDoc->CreateStaticClone(mDocShell); NS_ENSURE_STATE(mDocument); + CachePrintSelectionRanges(*aDoc, *mDocument); nsCOMPtr viewer; mDocShell->GetContentViewer(getter_AddRefs(viewer)); diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py index b91ee6561390..eff6148acf28 100644 --- a/xpcom/ds/StaticAtoms.py +++ b/xpcom/ds/StaticAtoms.py @@ -996,6 +996,7 @@ STATIC_ATOMS = [ Atom("previewDiv", "preview-div"), Atom("primary", "primary"), Atom("print", "print"), + Atom("printselectionranges", "printselectionranges"), Atom("priority", "priority"), Atom("processingInstruction", "processing-instruction"), Atom("profile", "profile"),