diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index a62043565bce..5aebcd14270a 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -231,6 +231,11 @@ struct RangePaintInfo { // offset of builder's reference frame to the root frame nsPoint mRootOffset; + // Resolution at which the items are normally painted. So if we're painting + // these items in a range separately from the "full display list", we may want + // to paint them at this resolution. + float mResolution = 1.0; + RangePaintInfo(nsRange* aRange, nsIFrame* aFrame) : mRange(aRange), mBuilder(aFrame, nsDisplayListBuilderMode::Painting, false) { @@ -4777,6 +4782,32 @@ UniquePtr PresShell::CreateRangePaintInfo( BuildDisplayListForNode(endContainer); } + // If one of the ancestor presShells (including this one) has a resolution + // set, we may have some APZ zoom applied. That means we may want to rasterize + // the nodes at that zoom level. Populate `info` with the relevant information + // so that the caller can decide what to do. Also wrap the display list in + // appropriate nsDisplayAsyncZoom display items. This code handles the general + // case with nested async zooms (even though that never actually happens), + // because it fell out of the implementation for free. + for (nsPresContext* ctx = GetPresContext(); ctx; + ctx = ctx->GetParentPresContext()) { + PresShell* shell = ctx->PresShell(); + float resolution = shell->GetResolution(); + if (resolution == 1.0) { + continue; + } + + info->mResolution *= resolution; + nsIFrame* rootScrollFrame = shell->GetRootScrollFrame(); + ViewID zoomedId = + nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent()); + + nsDisplayList wrapped; + wrapped.AppendNewToTop(&info->mBuilder, rootScrollFrame, + &info->mList, nullptr, zoomedId); + info->mList.AppendToTop(&wrapped); + } + #ifdef DEBUG if (gDumpRangePaintList) { fprintf(stderr, "CreateRangePaintInfo --- before ClipListToRange:\n"); @@ -4832,8 +4863,8 @@ already_AddRefed PresShell::PaintRangePaintInfo( // check if image-resizing-algorithm should be used if (aFlags & RenderImageFlags::IsImage) { // get max screensize - nscoord maxWidth = pc->AppUnitsToDevPixels(maxSize.width); - nscoord maxHeight = pc->AppUnitsToDevPixels(maxSize.height); + int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width); + int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height); // resize image relative to the screensize // get best height/width relative to screensize float bestHeight = float(maxHeight) * RELATIVE_SCALEFACTOR; @@ -4852,8 +4883,8 @@ already_AddRefed PresShell::PaintRangePaintInfo( scale = std::min(scale, adjustedScale); } else { // get half of max screensize - nscoord maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1); - nscoord maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1); + int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1); + int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1); if (pixelArea.width > maxWidth || pixelArea.height > maxHeight) { // divide the maximum size by the image size in both directions. // Whichever direction produces the smallest result determines how much @@ -4865,15 +4896,39 @@ already_AddRefed PresShell::PaintRangePaintInfo( } } - pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale); - pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale); - if (!pixelArea.width || !pixelArea.height) return nullptr; + // Pick a resolution scale factor that is the highest we need for any of + // the items. This means some items may get rendered at a higher-than-needed + // resolution but at least nothing will be avoidably blurry. + float resolutionScale = 1.0; + for (const UniquePtr& rangeInfo : aItems) { + resolutionScale = std::max(resolutionScale, rangeInfo->mResolution); + } + // Clamp the resolution scale so that `pixelArea` when scaled by `scale` and + // `resolutionScale` isn't bigger than `maxSize`. This prevents creating + // giant/unbounded images. + resolutionScale = + std::min(resolutionScale, maxSize.width / (scale * pixelArea.width)); + resolutionScale = + std::min(resolutionScale, maxSize.height / (scale * pixelArea.height)); + // The following assert should only get hit if pixelArea scaled by `scale` + // alone would already have been bigger than `maxSize`, which should never + // be the case. For release builds we handle gracefully by reverting + // resolutionScale to 1.0 to avoid unexpected consequences. + MOZ_ASSERT(resolutionScale >= 1.0); + resolutionScale = std::max(1.0f, resolutionScale); // adjust the screen position based on the rescaled size nscoord left = rootScreenRect.x + pixelArea.x; nscoord top = rootScreenRect.y + pixelArea.y; aScreenRect->x = NSToIntFloor(aPoint.x - float(aPoint.x - left) * scale); aScreenRect->y = NSToIntFloor(aPoint.y - float(aPoint.y - top) * scale); + + scale *= resolutionScale; + pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale); + pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale); + if (!pixelArea.width || !pixelArea.height) { + return nullptr; + } } else { // move aScreenRect to the position of the surface in screen coordinates aScreenRect->MoveTo(rootScreenRect.x + pixelArea.x, @@ -4914,7 +4969,9 @@ already_AddRefed PresShell::PaintRangePaintInfo( gfxMatrix initialTM = ctx->CurrentMatrixDouble(); - if (resize) initialTM.PreScale(scale, scale); + if (resize) { + initialTM.PreScale(scale, scale); + } // translate so that points are relative to the surface area gfxPoint surfaceOffset = nsLayoutUtils::PointToGfxPoint(