Bug 1653334 part 2: Cache the selection ranges on subdocuments as we build the nsPrintObject tree. r=jwatt

This also refactors the selection printing code, so that as we build the tree we
record which nsPrintObject should be used if printing a Selection is chosen.

Differential Revision: https://phabricator.services.mozilla.com/D85600
This commit is contained in:
Bob Owen 2020-08-03 14:23:56 +00:00
Родитель c4f92dc077
Коммит ada89eea81
5 изменённых файлов: 115 добавлений и 173 удалений

Просмотреть файл

@ -59,8 +59,10 @@ class nsPrintData {
nsCOMArray<nsIWebProgressListener> mPrintProgressListeners;
nsCOMPtr<nsIPrintProgressParams> mPrintProgressParams;
nsCOMPtr<nsPIDOMWindowOuter> mCurrentFocusWin; // cache a pointer to the
// currently focused window
// If there is a focused iframe, mSelectionRoot is set to its nsPrintObject.
// Otherwise, if there is a selection, it is set to the root nsPrintObject.
// Otherwise, it is unset.
nsPrintObject* mSelectionRoot = nullptr;
// Array of non-owning pointers to all the nsPrintObjects owned by this
// nsPrintData. This includes this->mPrintObject, as well as all of its

Просмотреть файл

@ -270,31 +270,6 @@ static bool IsParentAFrameSet(nsIDocShell* aParent) {
return isFrameSet;
}
static nsPrintObject* FindPrintObjectByDOMWin(nsPrintObject* aPO,
nsPIDOMWindowOuter* aDOMWin) {
NS_ASSERTION(aPO, "Pointer is null!");
// Often the CurFocused DOMWindow is passed in
// andit is valid for it to be null, so short circut
if (!aDOMWin) {
return nullptr;
}
nsCOMPtr<Document> doc = aDOMWin->GetDoc();
if (aPO->mDocument && aPO->mDocument->GetOriginalDocument() == doc) {
return aPO;
}
for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
nsPrintObject* po = FindPrintObjectByDOMWin(kid.get(), aDOMWin);
if (po) {
return po;
}
}
return nullptr;
}
static std::tuple<nsPageSequenceFrame*, int32_t>
GetSeqFrameAndCountPagesInternal(const UniquePtr<nsPrintObject>& aPO) {
if (!aPO) {
@ -320,18 +295,36 @@ GetSeqFrameAndCountPagesInternal(const UniquePtr<nsPrintObject>& aPO) {
}
/**
* The outparam aDocList returns a (depth first) flat list of all the
* nsPrintObjects created.
* Build a tree of nsPrintObjects under aPO. It also appends a (depth first)
* flat list of all the nsPrintObjects created to aPrintData->mPrintDocList. If
* one of the nsPrintObject's document is the focused document, then the print
* object is set as aPrintData->mSelectionRoot.
* @param aParentPO The parent nsPrintObject to populate, must not be null.
* @param aFocusedDoc Document from the window that had focus when print was
* initiated.
* @param aPrintData nsPrintData for the current print, must not be null.
*/
static void BuildNestedPrintObjects(Document* aDocument,
const UniquePtr<nsPrintObject>& aPO,
nsTArray<nsPrintObject*>* aDocList) {
MOZ_ASSERT(aDocument, "Pointer is null!");
MOZ_ASSERT(aDocList, "Pointer is null!");
MOZ_ASSERT(aPO, "Pointer is null!");
static void BuildNestedPrintObjects(const UniquePtr<nsPrintObject>& aParentPO,
const RefPtr<Document>& aFocusedDoc,
RefPtr<nsPrintData>& aPrintData) {
MOZ_ASSERT(aParentPO);
MOZ_ASSERT(aPrintData);
// If aParentPO is for an iframe and its original document is focusedDoc then
// always set as the selection root.
if (aParentPO->mFrameType == eIFrame &&
aParentPO->mDocument->GetOriginalDocument() == aFocusedDoc) {
aPrintData->mSelectionRoot = aParentPO.get();
} else if (!aPrintData->mSelectionRoot && aParentPO->mHasSelection) {
// If there is no focused iframe but there is a selection in one or more
// frames then we want to set the root nsPrintObject as the focus root so
// that later EnablePrintingSelectionOnly can search for and enable all
// nsPrintObjects containing selections.
aPrintData->mSelectionRoot = aPrintData->mPrintObject.get();
}
nsTArray<Document::PendingFrameStaticClone> pendingClones =
aDocument->TakePendingFrameStaticClones();
aParentPO->mDocument->TakePendingFrameStaticClones();
for (auto& clone : pendingClones) {
if (NS_WARN_IF(!clone.mStaticCloneOf)) {
continue;
@ -350,14 +343,26 @@ static void BuildNestedPrintObjects(Document* aDocument,
continue;
}
nsIDocShell* sourceDocShell =
clone.mStaticCloneOf->GetDocShell(IgnoreErrors());
if (!sourceDocShell) {
continue;
}
Document* sourceDoc = sourceDocShell->GetDocument();
if (!sourceDoc) {
continue;
}
auto childPO = MakeUnique<nsPrintObject>();
rv = childPO->InitAsNestedObject(docshell, doc, aPO.get());
rv = childPO->InitAsNestedObject(docshell, doc, sourceDoc, aParentPO.get());
if (NS_FAILED(rv)) {
MOZ_ASSERT_UNREACHABLE("Init failed?");
}
aPO->mKids.AppendElement(std::move(childPO));
aDocList->AppendElement(aPO->mKids.LastElement().get());
BuildNestedPrintObjects(doc, aPO->mKids.LastElement(), aDocList);
aPrintData->mPrintDocList.AppendElement(childPO.get());
BuildNestedPrintObjects(childPO, aFocusedDoc, aPrintData);
aParentPO->mKids.AppendElement(std::move(childPO));
}
}
@ -665,10 +670,8 @@ nsresult nsPrintJob::DoCommonPrint(bool aIsPrintPreview,
printData->mPrintProgressListeners.AppendObject(aWebProgressListener);
}
// Get the currently focused window and cache it
// because the Print Dialog will "steal" focus and later when you try
// to get the currently focused windows it will be nullptr
printData->mCurrentFocusWin = FindFocusedDOMWindow();
// Get the document from the currently focused window.
RefPtr<Document> focusedDoc = FindFocusedDocument();
// Get the docshell for this documentviewer
nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell, &rv));
@ -695,8 +698,7 @@ nsresult nsPrintJob::DoCommonPrint(bool aIsPrintPreview,
printData->mPrintObject->mFrameType =
printData->mIsParentAFrameSet ? eFrameSet : eDoc;
BuildNestedPrintObjects(printData->mPrintObject->mDocument,
printData->mPrintObject, &printData->mPrintDocList);
BuildNestedPrintObjects(printData->mPrintObject, focusedDoc, printData);
}
if (!aSourceDoc->IsStaticDocument()) {
@ -754,11 +756,8 @@ nsresult nsPrintJob::DoCommonPrint(bool aIsPrintPreview,
}
// Now determine how to set up the Frame print UI
bool isSelection = IsThereARangeSelection(printData->mCurrentFocusWin);
bool isIFrameSelected = IsThereAnIFrameSelected(
docShell, printData->mCurrentFocusWin, printData->mIsParentAFrameSet);
printData->mPrintSettings->SetPrintOptions(
nsIPrintSettings::kEnableSelectionRB, isSelection || isIFrameSelected);
nsIPrintSettings::kEnableSelectionRB, !!printData->mSelectionRoot);
bool printingViaParent =
XRE_IsContentProcess() && Preferences::GetBool("print.print_via_parent");
@ -1091,68 +1090,6 @@ void nsPrintJob::ShowPrintProgress(bool aIsForPrinting, bool& aDoNotify) {
}
}
//---------------------------------------------------------------------
bool nsPrintJob::IsThereARangeSelection(nsPIDOMWindowOuter* aDOMWin) {
if (mDisallowSelectionPrint || !aDOMWin) {
return false;
}
PresShell* presShell = aDOMWin->GetDocShell()->GetPresShell();
if (!presShell) {
return false;
}
// check here to see if there is a range selection
// so we know whether to turn on the "Selection" radio button
Selection* selection = presShell->GetCurrentSelection(SelectionType::eNormal);
if (!selection) {
return false;
}
int32_t rangeCount = selection->RangeCount();
if (!rangeCount) {
return false;
}
if (rangeCount > 1) {
return true;
}
// check to make sure it isn't an insertion selection
return selection->GetRangeAt(0) && !selection->IsCollapsed();
}
//---------------------------------------------------------------------
bool nsPrintJob::IsThereAnIFrameSelected(nsIDocShell* aDocShell,
nsPIDOMWindowOuter* aDOMWin,
bool& aIsParentFrameSet) {
aIsParentFrameSet = IsParentAFrameSet(aDocShell);
bool iFrameIsSelected = false;
if (mPrt && mPrt->mPrintObject) {
nsPrintObject* po =
FindPrintObjectByDOMWin(mPrt->mPrintObject.get(), aDOMWin);
iFrameIsSelected = po && po->mFrameType == eIFrame;
} else {
// First, check to see if we are a frameset
if (!aIsParentFrameSet) {
// Check to see if there is a currenlt focused frame
// if so, it means the selected frame is either the main docshell
// or an IFRAME
if (aDOMWin) {
// Get the main docshell's DOMWin to see if it matches
// the frame that is selected
nsPIDOMWindowOuter* domWin =
aDocShell ? aDocShell->GetWindow() : nullptr;
if (domWin != aDOMWin) {
iFrameIsSelected = true; // we have a selected IFRAME
}
}
}
}
return iFrameIsSelected;
}
// static
void nsPrintJob::GetDisplayTitleAndURL(Document& aDoc,
nsIPrintSettings* aSettings,
@ -1988,7 +1925,10 @@ nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) {
int16_t printRangeType = nsIPrintSettings::kRangeAllPages;
printData->mPrintSettings->GetPrintRange(&printRangeType);
if (printRangeType == nsIPrintSettings::kRangeSelection) {
DeleteNonSelectedNodes(*aPO->mDocument);
// If we fail to remove the nodes then we should fail to print, because if
// the user was trying to print a small selection from a large document,
// sending the whole document to a real printer would be very frustrating.
MOZ_TRY(DeleteNonSelectedNodes(*aPO->mDocument));
}
bool doReturn = false;
@ -2161,6 +2101,11 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DeleteNonSelectedNodes(
MOZ_ASSERT(!selection->RangeCount());
nsINode* bodyNode = aDoc.GetBodyElement();
if (!bodyNode) {
// We don't currently support printing selections that are not in a body.
return NS_ERROR_FAILURE;
}
nsINode* startNode = bodyNode;
uint32_t startOffset = 0;
@ -2499,10 +2444,7 @@ void nsPrintJob::SetIsPrintPreview(bool aIsPrintPreview) {
}
}
/** ---------------------------------------------------
* Get the Focused Frame for a documentviewer
*/
already_AddRefed<nsPIDOMWindowOuter> nsPrintJob::FindFocusedDOMWindow() const {
Document* nsPrintJob::FindFocusedDocument() const {
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
NS_ENSURE_TRUE(fm, nullptr);
@ -2519,7 +2461,7 @@ already_AddRefed<nsPIDOMWindowOuter> nsPrintJob::FindFocusedDOMWindow() const {
NS_ENSURE_TRUE(focusedWindow, nullptr);
if (IsWindowsInOurSubTree(focusedWindow)) {
return focusedWindow.forget();
return focusedWindow->GetDoc();
}
return nullptr;
@ -2619,50 +2561,20 @@ nsresult nsPrintJob::EnablePOsForPrinting() {
return NS_OK;
}
// This means we are either printed a selected IFrame or
// we are printing the current selection
// This means we are either printing a selected iframe or
// we are printing the current selection.
MOZ_ASSERT(printRangeType == nsIPrintSettings::kRangeSelection);
NS_ENSURE_STATE(printData->mSelectionRoot);
// If the currentFocusDOMWin can'r be null if something is selected
if (!printData->mCurrentFocusWin) {
for (uint32_t i = 0; i < printData->mPrintDocList.Length(); i++) {
nsPrintObject* po = printData->mPrintDocList.ElementAt(i);
NS_ASSERTION(po, "nsPrintObject can't be null!");
nsCOMPtr<nsPIDOMWindowOuter> domWin = po->mDocShell->GetWindow();
if (IsThereARangeSelection(domWin)) {
printData->mCurrentFocusWin = std::move(domWin);
po->EnablePrinting(true);
break;
}
}
return NS_OK;
// If mSelectionRoot is a selected iframe without a selection, then just
// enable normally from that point.
if (printData->mSelectionRoot->mFrameType == eIFrame &&
!printData->mSelectionRoot->mHasSelection) {
printData->mSelectionRoot->EnablePrinting(true);
} else {
// Otherwise, only enable nsPrintObjects that have a selection.
printData->mSelectionRoot->EnablePrintingSelectionOnly();
}
// Find the selected IFrame
nsPrintObject* po = FindPrintObjectByDOMWin(printData->mPrintObject.get(),
printData->mCurrentFocusWin);
if (!po) {
return NS_OK;
}
// Now, only enable this POs (the selected PO) and all of its children
po->EnablePrinting(true);
// check to see if we have a range selection,
// as oppose to a insert selection
// this means if the user just clicked on the IFrame then
// there will not be a selection so we want the entire page to print
//
// XXX this is sort of a hack right here to make the page
// not try to reposition itself when printing selection
nsPIDOMWindowOuter* domWin =
po->mDocument->GetOriginalDocument()->GetWindow();
if (!IsThereARangeSelection(domWin)) {
printRangeType = nsIPrintSettings::kRangeAllPages;
printData->mPrintSettings->SetPrintRange(printRangeType);
}
PR_PL(("PrintRange: %s \n", gPrintRangeStr[printRangeType]));
return NS_OK;
}

Просмотреть файл

@ -164,8 +164,6 @@ class nsPrintJob final : public nsIObserver,
nsresult& aStatus);
nsresult DoPrint(const mozilla::UniquePtr<nsPrintObject>& aPO);
void SetPrintPO(nsPrintObject* aPO, bool aPrint);
/**
* Filters out certain user events while Print Preview is open to prevent
* the user from interacting with the Print Preview document and breaking
@ -186,17 +184,14 @@ class nsPrintJob final : public nsIObserver,
nsIPrintProgressParams* aParams);
void EllipseLongString(nsAString& aStr, const uint32_t aLen, bool aDoFront);
bool IsThereARangeSelection(nsPIDOMWindowOuter* aDOMWin);
nsresult StartPagePrintTimer(const mozilla::UniquePtr<nsPrintObject>& aPO);
bool IsWindowsInOurSubTree(nsPIDOMWindowOuter* aDOMWindow) const;
bool IsThereAnIFrameSelected(nsIDocShell* aDocShell,
nsPIDOMWindowOuter* aDOMWin,
bool& aIsParentFrameSet);
// get the currently infocus frame for the document viewer
already_AddRefed<nsPIDOMWindowOuter> FindFocusedDOMWindow() const;
/**
* @return The document from the focused windows for a document viewer.
*/
Document* FindFocusedDocument() const;
/// Customizes the behaviour of GetDisplayTitleAndURL.
enum class DocTitleDefault : uint32_t { eDocURLElseFallback, eFallback };

Просмотреть файл

@ -104,8 +104,12 @@ static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
* 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.
*
* @param aSourceDoc the document from which we are caching selection ranges
* @param aStaticClone the document that will hold the cache
* @return true if a selection range was cached
*/
static void CachePrintSelectionRanges(const Document& aSourceDoc,
static bool CachePrintSelectionRanges(const Document& aSourceDoc,
Document& aStaticClone) {
MOZ_ASSERT(aStaticClone.IsStaticDocument());
MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
@ -122,12 +126,12 @@ static void CachePrintSelectionRanges(const Document& aSourceDoc,
}
if (!origSelection && !origRanges) {
return;
return false;
}
size_t rangeCount =
sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
auto* printRanges = new nsTArray<RefPtr<nsRange>>(rangeCount);
auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
for (size_t i = 0; i < rangeCount; ++i) {
const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
@ -156,8 +160,14 @@ static void CachePrintSelectionRanges(const Document& aSourceDoc,
}
}
aStaticClone.SetProperty(nsGkAtoms::printselectionranges, printRanges,
if (printRanges->IsEmpty()) {
return false;
}
aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
printRanges.release(),
nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
return true;
}
nsresult nsPrintObject::InitAsRootObject(nsIDocShell* aDocShell, Document* aDoc,
@ -198,7 +208,7 @@ nsresult nsPrintObject::InitAsRootObject(nsIDocShell* aDocShell, Document* aDoc,
mDocument = aDoc->CreateStaticClone(mDocShell);
NS_ENSURE_STATE(mDocument);
CachePrintSelectionRanges(*aDoc, *mDocument);
mHasSelection = CachePrintSelectionRanges(*aDoc, *mDocument);
nsCOMPtr<nsIContentViewer> viewer;
mDocShell->GetContentViewer(getter_AddRefs(viewer));
@ -210,6 +220,7 @@ nsresult nsPrintObject::InitAsRootObject(nsIDocShell* aDocShell, Document* aDoc,
nsresult nsPrintObject::InitAsNestedObject(nsIDocShell* aDocShell,
Document* aDoc,
Document* aSourceOfDoc,
nsPrintObject* aParent) {
NS_ENSURE_STATE(aDocShell);
NS_ENSURE_STATE(aDoc);
@ -231,6 +242,12 @@ nsresult nsPrintObject::InitAsNestedObject(nsIDocShell* aDocShell,
mFrameType = eIFrame;
}
// We need to use the document that aDoc was cloned from (aSourceOfDoc) and
// not aDoc->GetOriginalDocument(), because if aSourceOfDoc is a static clone
// aDoc->GetOriginalDocument() will be aSourceOfDoc's original document, which
// may now contain a different or no selection.
mHasSelection = CachePrintSelectionRanges(*aSourceOfDoc, *aDoc);
return NS_OK;
}
@ -255,3 +272,11 @@ void nsPrintObject::EnablePrinting(bool aEnable) {
kid->EnablePrinting(aEnable);
}
}
void nsPrintObject::EnablePrintingSelectionOnly() {
mPrintingIsEnabled = mHasSelection;
for (const UniquePtr<nsPrintObject>& kid : mKids) {
kid->EnablePrintingSelectionOnly();
}
}

Просмотреть файл

@ -38,6 +38,7 @@ class nsPrintObject {
bool aForPrintPreview);
nsresult InitAsNestedObject(nsIDocShell* aDocShell,
mozilla::dom::Document* aDoc,
mozilla::dom::Document* aSourceOfDoc,
nsPrintObject* aParent);
void DestroyPresentation();
@ -47,6 +48,12 @@ class nsPrintObject {
* from the given item down into the tree
*/
void EnablePrinting(bool aEnable);
/**
* Recursively sets all the PO items to be printed if they have a selection.
*/
void EnablePrintingSelectionOnly();
bool PrintingIsEnabled() const { return mPrintingIsEnabled; }
// Data Members
@ -66,6 +73,7 @@ class nsPrintObject {
bool mHasBeenPrinted;
bool mInvisible; // Indicates PO is set to not visible by CSS
bool mDidCreateDocShell;
bool mHasSelection = false;
float mShrinkRatio;
float mZoomRatio;