diff --git a/build/cromite_patches_list.txt b/build/cromite_patches_list.txt index 3ca73a73..82d99987 100644 --- a/build/cromite_patches_list.txt +++ b/build/cromite_patches_list.txt @@ -304,6 +304,7 @@ Keep-disabled-cache-sharing-for-pervasive-scripts.patch High-Resolution-Timing-Mitigation.patch Enable-snackbar-swipe.patch Add-cromite-test-support.patch +Android-Pixel-Perfect-Mode.patch Temp-disable-UseContextSnapshot.patch # temporary or wip patches diff --git a/build/patches/Android-Pixel-Perfect-Mode.patch b/build/patches/Android-Pixel-Perfect-Mode.patch new file mode 100644 index 00000000..19f6f5dd --- /dev/null +++ b/build/patches/Android-Pixel-Perfect-Mode.patch @@ -0,0 +1,2574 @@ +From: uazo +Date: Mon, 25 Aug 2025 10:50:06 +0000 +Subject: Android Pixel Perfect Mode + +Eliminate conversions between dpi and pixels in Blink. +The goal (not yet achieved) is to mask the display geometry of Android devices and to make +the various devices identical for canvas and rect fingerprinting. +This patch is WIP, it is incomplete and contains visual glitch, do not activate. +This version contains points of interest marked in the code, which will be removed once the patch is complete. + +License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html +--- + cc/input/input_handler.cc | 2 +- + cc/input/scrollbar_controller.cc | 2 +- + cc/layers/viewport.cc | 4 +- + cc/mojo_embedder/viz_layer_context.cc | 2 +- + cc/trees/commit_state.cc | 1 + + cc/trees/commit_state.h | 1 + + cc/trees/layer_tree_host.cc | 5 +- + cc/trees/layer_tree_host.h | 4 ++ + cc/trees/layer_tree_host_impl.cc | 4 +- + cc/trees/layer_tree_impl.cc | 30 ++++++++---- + cc/trees/layer_tree_impl.h | 6 +++ + cc/trees/property_tree.cc | 4 +- + .../java/res/xml/developer_preferences.xml | 4 ++ + .../chrome/browser/WarmupManager.java | 2 +- + .../chrome/browser/app/ChromeActivity.java | 23 ++++++++- + .../compositor/CompositorViewHolder.java | 4 +- + .../bottombar/OverlayPanelContent.java | 2 +- + .../chromium/chrome/browser/tab/TabImpl.java | 4 +- + .../tracing/settings/DeveloperSettings.java | 10 ++++ + .../bottombar/overlay_panel_content.cc | 2 +- + .../android/compositor/tab_content_manager.cc | 6 ++- + .../browser/flags/cromite/sPixelPerfect.java | 33 +++++++++++++ + .../settings/ChromeBaseSettingsFragment.java | 4 ++ + .../Pixel-perfect-mode.grdp | 9 ++++ + .../common/interventions_features.cc | 1 + + components/input/android_input_helper.cc | 2 +- + components/input/child_frame_input_helper.cc | 2 +- + components/input/input_router_impl.cc | 2 +- + .../render_widget_host_input_event_router.cc | 5 +- + .../input/render_widget_host_view_input.cc | 3 ++ + components/input/render_widget_targeter.cc | 4 +- + .../thin_webview/internal/thin_webview.cc | 2 +- + components/viz/service/input/input_manager.cc | 2 +- + .../render_input_router_support_android.cc | 2 +- + ...render_input_router_support_child_frame.cc | 2 +- + .../viz/service/layers/layer_context_impl.cc | 2 +- + .../selection/selection_popup_controller.cc | 5 +- + .../cross_process_frame_connector.cc | 10 ++-- + .../renderer_host/delegated_frame_host.cc | 2 +- + ...selection_controller_client_child_frame.cc | 2 +- + .../renderer_host/render_frame_host_impl.cc | 2 +- + .../renderer_host/render_widget_host_impl.cc | 30 ++++++++++-- + .../render_widget_host_view_android.cc | 17 +++++-- + .../render_widget_host_view_android.h | 1 + + .../render_widget_host_view_base.cc | 6 ++- + .../render_widget_host_view_base.h | 2 + + .../render_widget_host_view_child_frame.cc | 4 ++ + .../render_widget_host_view_child_frame.h | 1 + + .../web_contents/web_contents_android.cc | 7 ++- + .../web_contents/web_contents_android.h | 2 +- + content/child/runtime_features.cc | 6 ++- + .../SelectionPopupControllerImpl.java | 5 +- + .../browser/webcontents/WebContentsImpl.java | 6 +-- + .../content_public/browser/WebContents.java | 2 +- + content/public/common/content_features.cc | 4 ++ + .../about_flags_cc/Pixel-perfect-mode.inc | 7 +++ + .../Pixel-perfect-mode.inc | 1 + + .../content_features_h/Pixel-perfect-mode.inc | 1 + + .../mojom/compositing/layer_context.mojom | 1 + + .../blink/common/widget/visual_properties.cc | 4 ++ + .../widget/visual_properties_mojom_traits.cc | 3 ++ + .../public/common/widget/visual_properties.h | 4 ++ + .../widget/visual_properties_mojom_traits.h | 11 +++++ + .../mojom/widget/visual_properties.mojom | 4 ++ + third_party/blink/public/web/web_view.h | 1 + + .../canvas_interventions_helper.cc | 1 + + .../blink/renderer/core/css/media_values.cc | 4 +- + .../renderer/core/exported/web_view_impl.cc | 41 ++++++++++++---- + .../renderer/core/exported/web_view_impl.h | 5 ++ + .../renderer/core/frame/local_frame_view.cc | 5 +- + .../renderer/core/frame/visual_viewport.cc | 34 ++++++++++++- + .../renderer/core/frame/visual_viewport.h | 7 ++- + .../core/frame/web_frame_widget_impl.cc | 36 ++++++++++++-- + .../core/frame/web_frame_widget_impl.h | 1 + + .../core/frame/web_local_frame_impl.cc | 4 +- + .../core/html/canvas/html_canvas_element.cc | 1 + + .../renderer/core/input/event_handler.cc | 2 +- + .../inspector/inspector_emulation_agent.cc | 3 +- + third_party/blink/renderer/core/page/page.cc | 4 ++ + .../core/page/viewport_description.cc | 2 +- + .../canvas2d/base_rendering_context_2d.cc | 5 +- + .../platform/runtime_enabled_features.json5 | 6 ++- + .../widget/input/input_handler_proxy.cc | 48 +++++++++++++------ + .../renderer/platform/widget/widget_base.cc | 26 +++++----- + .../renderer/platform/widget/widget_base.h | 2 +- + ui/android/delegated_frame_host_android.cc | 2 +- + ui/android/event_forwarder.cc | 14 +++--- + .../ui/base/UiAndroidFeatureList.java | 6 +++ + .../ui/display/PhysicalDisplayAndroid.java | 10 ++++ + ui/android/view_android.cc | 27 ++++++++--- + ui/android/view_android.h | 9 +++- + ui/display/mojom/screen_info.mojom | 1 + + ui/display/mojom/screen_info_mojom_traits.cc | 1 + + ui/display/mojom/screen_info_mojom_traits.h | 4 ++ + ui/display/screen_info.h | 1 + + ui/events/blink/blink_event_util.cc | 2 +- + 96 files changed, 525 insertions(+), 137 deletions(-) + create mode 100644 chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sPixelPerfect.java + create mode 100644 chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Pixel-perfect-mode.grdp + create mode 100644 cromite_flags/chrome/browser/about_flags_cc/Pixel-perfect-mode.inc + create mode 100644 cromite_flags/content/public/common/content_features_cc/Pixel-perfect-mode.inc + create mode 100644 cromite_flags/content/public/common/content_features_h/Pixel-perfect-mode.inc + +diff --git a/cc/input/input_handler.cc b/cc/input/input_handler.cc +--- a/cc/input/input_handler.cc ++++ b/cc/input/input_handler.cc +@@ -671,7 +671,7 @@ InputHandlerPointerResult InputHandler::MouseMoveAt( + return result; + } + +-PointerResultType InputHandler::HitTest(const gfx::PointF& viewport_point) { ++PointerResultType InputHandler::HitTest(const gfx::PointF& viewport_point) { // POI + return scrollbar_controller_->HitTest(viewport_point) + ? PointerResultType::kScrollbarScroll + : PointerResultType::kUnhandled; +diff --git a/cc/input/scrollbar_controller.cc b/cc/input/scrollbar_controller.cc +--- a/cc/input/scrollbar_controller.cc ++++ b/cc/input/scrollbar_controller.cc +@@ -658,7 +658,7 @@ InputHandlerPointerResult ScrollbarController::HandlePointerUp( + } + + // Returns the layer that is hit by the position_in_widget. +-LayerImpl* ScrollbarController::GetLayerHitByPoint( ++LayerImpl* ScrollbarController::GetLayerHitByPoint( // POI + const gfx::PointF position_in_widget) const { + LayerTreeImpl* active_tree = layer_tree_host_impl_->active_tree(); + gfx::Point viewport_point(position_in_widget.x(), position_in_widget.y()); +diff --git a/cc/layers/viewport.cc b/cc/layers/viewport.cc +--- a/cc/layers/viewport.cc ++++ b/cc/layers/viewport.cc +@@ -38,7 +38,7 @@ void Viewport::Pan(const gfx::Vector2dF& delta) { + host_impl_->active_tree()); + } + +-Viewport::ScrollResult Viewport::ScrollBy(const gfx::Vector2dF& physical_delta, ++Viewport::ScrollResult Viewport::ScrollBy(const gfx::Vector2dF& physical_delta, // POI + const gfx::Point& viewport_point, + bool is_direct_manipulation, + bool affect_browser_controls, +@@ -123,7 +123,7 @@ void Viewport::SnapIfNeeded() { + ScrollBy(delta, gfx::Point(), false, false, true); + } + +-gfx::Vector2dF Viewport::ComputeClampedDelta( ++gfx::Vector2dF Viewport::ComputeClampedDelta( // POI + const gfx::Vector2dF& scroll_delta) const { + // When clamping for the outer viewport, we need to distribute the scroll + // between inner and outer to get the clamped value. The returned values +diff --git a/cc/mojo_embedder/viz_layer_context.cc b/cc/mojo_embedder/viz_layer_context.cc +--- a/cc/mojo_embedder/viz_layer_context.cc ++++ b/cc/mojo_embedder/viz_layer_context.cc +@@ -1238,7 +1238,7 @@ void VizLayerContext::SetVisible(bool visible) { + service_->SetVisible(visible); + } + +-void VizLayerContext::UpdateDisplayTreeFrom( ++void VizLayerContext::UpdateDisplayTreeFrom( // POI + LayerTreeImpl& tree, + viz::ClientResourceProvider& resource_provider, + viz::RasterContextProvider& context_provider, +diff --git a/cc/trees/commit_state.cc b/cc/trees/commit_state.cc +--- a/cc/trees/commit_state.cc ++++ b/cc/trees/commit_state.cc +@@ -25,6 +25,7 @@ CommitState::CommitState(const CommitState& prev) + external_page_scale_factor(prev.external_page_scale_factor), + max_page_scale_factor(prev.max_page_scale_factor), + min_page_scale_factor(prev.min_page_scale_factor), ++ browser_control_scale_factor(prev.browser_control_scale_factor), + page_scale_factor(prev.page_scale_factor), + painted_device_scale_factor(prev.painted_device_scale_factor), + top_controls_shown_ratio(prev.top_controls_shown_ratio), +diff --git a/cc/trees/commit_state.h b/cc/trees/commit_state.h +--- a/cc/trees/commit_state.h ++++ b/cc/trees/commit_state.h +@@ -93,6 +93,7 @@ struct CC_EXPORT CommitState { + float external_page_scale_factor = 1.f; + float max_page_scale_factor = 1.f; + float min_page_scale_factor = 1.f; ++ float browser_control_scale_factor = 1.f; + float page_scale_factor = 1.f; + float painted_device_scale_factor = 1.f; + float top_controls_shown_ratio = 0.f; +diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc +--- a/cc/trees/layer_tree_host.cc ++++ b/cc/trees/layer_tree_host.cc +@@ -1348,7 +1348,7 @@ void LayerTreeHost::SetEventListenerProperties( + SetNeedsCommit(); + } + +-void LayerTreeHost::SetViewportRectAndScale( ++void LayerTreeHost::SetViewportRectAndScale( // POI + const gfx::Rect& device_viewport_rect, + float device_scale_factor, + const viz::LocalSurfaceId& local_surface_id_from_parent) { +@@ -1459,9 +1459,11 @@ void LayerTreeHost::SetOverscrollBehavior(const OverscrollBehavior& behavior) { + + void LayerTreeHost::SetPageScaleFactorAndLimits(float page_scale_factor, + float min_page_scale_factor, ++ float browser_control_scale_factor, + float max_page_scale_factor) { + if (pending_commit_state()->page_scale_factor == page_scale_factor && + pending_commit_state()->min_page_scale_factor == min_page_scale_factor && ++ pending_commit_state()->browser_control_scale_factor == browser_control_scale_factor && + pending_commit_state()->max_page_scale_factor == max_page_scale_factor) + return; + DCHECK_GE(page_scale_factor, min_page_scale_factor); +@@ -1477,6 +1479,7 @@ void LayerTreeHost::SetPageScaleFactorAndLimits(float page_scale_factor, + + pending_commit_state()->page_scale_factor = page_scale_factor; + pending_commit_state()->min_page_scale_factor = min_page_scale_factor; ++ pending_commit_state()->browser_control_scale_factor = browser_control_scale_factor; + pending_commit_state()->max_page_scale_factor = max_page_scale_factor; + SetPropertyTreesNeedRebuild(); + SetNeedsCommit(); +diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h +--- a/cc/trees/layer_tree_host.h ++++ b/cc/trees/layer_tree_host.h +@@ -565,6 +565,7 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { + + void SetPageScaleFactorAndLimits(float page_scale_factor, + float min_page_scale_factor, ++ float browser_control_scale_factor, + float max_page_scale_factor); + float page_scale_factor() const { + return pending_commit_state()->page_scale_factor; +@@ -572,6 +573,9 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { + float min_page_scale_factor() const { + return pending_commit_state()->min_page_scale_factor; + } ++ float browser_control_scale_factor() const { ++ return pending_commit_state()->browser_control_scale_factor; ++ } + float max_page_scale_factor() const { + return pending_commit_state()->max_page_scale_factor; + } +diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc +--- a/cc/trees/layer_tree_host_impl.cc ++++ b/cc/trees/layer_tree_host_impl.cc +@@ -2487,7 +2487,7 @@ viz::RegionCaptureBounds LayerTreeHostImpl::CollectRegionCaptureBounds() { + return bounds; + } + +-viz::CompositorFrameMetadata LayerTreeHostImpl::MakeCompositorFrameMetadata() { ++viz::CompositorFrameMetadata LayerTreeHostImpl::MakeCompositorFrameMetadata() { // POI + viz::CompositorFrameMetadata metadata; + metadata.frame_token = ++next_frame_token_; + metadata.device_scale_factor = active_tree_->painted_device_scale_factor() * +@@ -2646,7 +2646,7 @@ viz::CompositorFrameMetadata LayerTreeHostImpl::MakeCompositorFrameMetadata() { + return metadata; + } + +-RenderFrameMetadata LayerTreeHostImpl::MakeRenderFrameMetadata( ++RenderFrameMetadata LayerTreeHostImpl::MakeRenderFrameMetadata( // POI + FrameData* frame) { + RenderFrameMetadata metadata; + metadata.root_scroll_offset = active_tree_->TotalScrollOffset(); +diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc +--- a/cc/trees/layer_tree_impl.cc ++++ b/cc/trees/layer_tree_impl.cc +@@ -161,6 +161,7 @@ LayerTreeImpl::LayerTreeImpl( + background_color_(SkColors::kTransparent), + page_scale_factor_(page_scale_factor), + min_page_scale_factor_(0), ++ browser_control_scale_factor_(0), + max_page_scale_factor_(0), + external_page_scale_factor_(1.f), + device_scale_factor_(1.f), +@@ -449,7 +450,7 @@ void LayerTreeImpl::InvalidateRasterInducingScrolls( + } + } + +-void LayerTreeImpl::UpdateViewportContainerSizes() { ++void LayerTreeImpl::UpdateViewportContainerSizes() { // POI + if (!InnerViewportScrollNode()) + return; + +@@ -527,8 +528,13 @@ void LayerTreeImpl::UpdateViewportContainerSizes() { + // Adjust the outer viewport container as well, since adjusting only the + // inner may cause its bounds to exceed those of the outer, causing scroll + // clamping. +- gfx::Vector2dF scaled_bounds_delta = +- gfx::ScaleVector2d(bounds_delta, 1.f / min_page_scale_factor()); ++ gfx::Vector2dF scaled_bounds_delta; ++ if (browser_control_scale_factor() != 1.f) { ++ scaled_bounds_delta = ++ gfx::ScaleVector2d(bounds_delta, 1.f / browser_control_scale_factor()); ++ } else { ++ scaled_bounds_delta = gfx::ScaleVector2d(bounds_delta, 1.f / min_page_scale_factor()); ++ } + + property_trees->SetOuterViewportContainerBoundsDelta(scaled_bounds_delta); + // outer_viewport_container_bounds_delta and +@@ -819,6 +825,7 @@ void LayerTreeImpl::PullLayerTreePropertiesFrom(CommitState& commit_state) { + + PushPageScaleFromMainThread(commit_state.page_scale_factor, + commit_state.min_page_scale_factor, ++ commit_state.browser_control_scale_factor, + commit_state.max_page_scale_factor); + + SetBrowserControlsParams(commit_state.browser_controls_params); +@@ -930,6 +937,7 @@ void LayerTreeImpl::PushPropertiesTo(LayerTreeImpl* target_tree) { + // Active tree already shares the page_scale_factor object with pending + // tree so only the limits need to be provided. + target_tree->PushPageScaleFactorAndLimits(nullptr, min_page_scale_factor(), ++ browser_control_scale_factor(), + max_page_scale_factor()); + target_tree->SetExternalPageScaleFactor(external_page_scale_factor_); + +@@ -1311,19 +1319,21 @@ void LayerTreeImpl::SetPageScaleOnActiveTree(float active_page_scale) { + + void LayerTreeImpl::PushPageScaleFromMainThread(float page_scale_factor, + float min_page_scale_factor, ++ float browser_control_scale_factor, + float max_page_scale_factor) { +- PushPageScaleFactorAndLimits(&page_scale_factor, min_page_scale_factor, ++ PushPageScaleFactorAndLimits(&page_scale_factor, min_page_scale_factor, browser_control_scale_factor, + max_page_scale_factor); + } + + void LayerTreeImpl::SetPageScaleFactorAndLimitsForDisplayTree( + float page_scale_factor, + float min_page_scale_factor, ++ float browser_control_scale_factor, + float max_page_scale_factor) { + DCHECK(settings().trees_in_viz_in_viz_process); + bool changed_page_scale = page_scale_factor_->SetCurrent(page_scale_factor); + changed_page_scale |= +- SetPageScaleFactorLimits(min_page_scale_factor, max_page_scale_factor); ++ SetPageScaleFactorLimits(min_page_scale_factor, browser_control_scale_factor, max_page_scale_factor); + + if (changed_page_scale) { + DidUpdatePageScale(); +@@ -1332,12 +1342,13 @@ void LayerTreeImpl::SetPageScaleFactorAndLimitsForDisplayTree( + + void LayerTreeImpl::PushPageScaleFactorAndLimits(const float* page_scale_factor, + float min_page_scale_factor, ++ float browser_control_scale_factor, + float max_page_scale_factor) { + DCHECK(page_scale_factor || IsActiveTree()); + bool changed_page_scale = false; + + changed_page_scale |= +- SetPageScaleFactorLimits(min_page_scale_factor, max_page_scale_factor); ++ SetPageScaleFactorLimits(min_page_scale_factor, browser_control_scale_factor, max_page_scale_factor); + + if (page_scale_factor) { + DCHECK(!IsActiveTree() || !host_impl_->pending_tree()); +@@ -1442,12 +1453,15 @@ void LayerTreeImpl::PushBrowserControls( + } + + bool LayerTreeImpl::SetPageScaleFactorLimits(float min_page_scale_factor, ++ float browser_control_scale_factor, + float max_page_scale_factor) { + if (min_page_scale_factor == min_page_scale_factor_ && ++ browser_control_scale_factor == browser_control_scale_factor_ && + max_page_scale_factor == max_page_scale_factor_) + return false; + + min_page_scale_factor_ = min_page_scale_factor; ++ browser_control_scale_factor_ = browser_control_scale_factor; + max_page_scale_factor_ = max_page_scale_factor; + + return true; +@@ -2366,7 +2380,7 @@ ScrollbarSet LayerTreeImpl::ScrollbarsFor(ElementId scroll_element_id) const { + return scrollbars; + } + +-static bool PointHitsRect( ++static bool PointHitsRect( // POI + const gfx::PointF& screen_space_point, + const gfx::Transform& local_space_to_screen_space_transform, + const gfx::Rect& local_space_rect, +@@ -2952,7 +2966,7 @@ static gfx::SelectionBound ComputeViewportSelectionBound( + return viewport_bound; + } + +-void LayerTreeImpl::GetViewportSelection( ++void LayerTreeImpl::GetViewportSelection( // POI + viz::Selection* selection) { + DCHECK(selection); + +diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h +--- a/cc/trees/layer_tree_impl.h ++++ b/cc/trees/layer_tree_impl.h +@@ -380,7 +380,9 @@ class CC_EXPORT LayerTreeImpl { + void SetPageScaleOnActiveTree(float active_page_scale); + void PushPageScaleFromMainThread(float page_scale_factor, + float min_page_scale_factor, ++ float browser_control_scale_factor, + float max_page_scale_factor); ++ float browser_control_scale_factor() const { return browser_control_scale_factor_; } + float current_page_scale_factor() const { + return page_scale_factor()->Current(IsActiveTree()); + } +@@ -845,6 +847,7 @@ class CC_EXPORT LayerTreeImpl { + + void SetPageScaleFactorAndLimitsForDisplayTree(float page_scale_factor, + float min_page_scale_factor, ++ float browser_control_scale_factor, + float max_page_scale_factor); + + LayerTreeHostImpl* host_impl() { return host_impl_; } +@@ -869,8 +872,10 @@ class CC_EXPORT LayerTreeImpl { + float ClampPageScaleFactorToLimits(float page_scale_factor) const; + void PushPageScaleFactorAndLimits(const float* page_scale_factor, + float min_page_scale_factor, ++ float browser_control_scale_factor, + float max_page_scale_factor); + bool SetPageScaleFactorLimits(float min_page_scale_factor, ++ float browser_control_scale_factor, + float max_page_scale_factor); + void DidUpdatePageScale(); + void PushBrowserControls(const float* top_controls_shown_ratio, +@@ -909,6 +914,7 @@ class CC_EXPORT LayerTreeImpl { + + scoped_refptr page_scale_factor_; + float min_page_scale_factor_; ++ float browser_control_scale_factor_; + float max_page_scale_factor_; + float external_page_scale_factor_; + +diff --git a/cc/trees/property_tree.cc b/cc/trees/property_tree.cc +--- a/cc/trees/property_tree.cc ++++ b/cc/trees/property_tree.cc +@@ -1587,7 +1587,7 @@ void ScrollTree::clear() { + #endif + } + +-gfx::PointF ScrollTree::MaxScrollOffset(int scroll_node_id) const { ++gfx::PointF ScrollTree::MaxScrollOffset(int scroll_node_id) const { // + const ScrollNode* scroll_node = Node(scroll_node_id); + gfx::SizeF scroll_bounds = this->scroll_bounds(scroll_node_id); + +@@ -2079,7 +2079,7 @@ gfx::Vector2dF ScrollTree::ScrollBy(const ScrollNode& scroll_node, + return old_offset + scroll - new_offset; + } + +-gfx::PointF ScrollTree::ClampScrollOffsetToLimits( ++gfx::PointF ScrollTree::ClampScrollOffsetToLimits( // POI + gfx::PointF offset, + const ScrollNode& scroll_node) const { + offset.SetToMin(MaxScrollOffset(scroll_node.id)); +diff --git a/chrome/android/java/res/xml/developer_preferences.xml b/chrome/android/java/res/xml/developer_preferences.xml +--- a/chrome/android/java/res/xml/developer_preferences.xml ++++ b/chrome/android/java/res/xml/developer_preferences.xml +@@ -40,4 +40,8 @@ found in the LICENSE file. + android:summary="@string/android_fonts_fingerprinting_mitigation_summary" + app:featureName="android-fonts-fingerprinting-mitigation" + app:needRestart="true" /> ++ + +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/WarmupManager.java b/chrome/android/java/src/org/chromium/chrome/browser/WarmupManager.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/WarmupManager.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/WarmupManager.java +@@ -312,7 +312,7 @@ public class WarmupManager { + Rect bounds = TabUtils.estimateContentSize(context); + int width = bounds.right - bounds.left; + int height = bounds.bottom - bounds.top; +- tab.getWebContents().setSize(width, height); ++ tab.getWebContents().setSize(width, height, 0); + + // Reparent the tab to detach it from the current activity. + ReparentingTask.from(tab).detach(); +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java +@@ -36,6 +36,9 @@ import androidx.annotation.NonNull; + import androidx.annotation.Nullable; + import androidx.annotation.VisibleForTesting; + ++import android.util.DisplayMetrics; ++import org.chromium.chrome.browser.flags.cromite.sPixelPerfect; ++ + import org.chromium.base.ActivityState; + import org.chromium.base.ApplicationStatus; + import org.chromium.base.BuildInfo; +@@ -509,7 +512,25 @@ public abstract class ChromeActivity extends AsyncInitializationActivity + + // Ensure that mConfig is initialized before tablet mode changes. + mConfig = getResources().getConfiguration(); +- ++ if (sPixelPerfect.getInstance().isEnabled()) { ++ DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); ++ Log.i("","Pixel Perfect Mode Enabled:" ++ + " density=" + displayMetrics.density ++ + " densityDpi=" + mConfig.densityDpi ++ + " xdpi=" + displayMetrics.xdpi ++ + " ydpi=" + displayMetrics.ydpi ++ + " widthPixels=" + displayMetrics.widthPixels ++ + " heightPixels=" + displayMetrics.heightPixels ++ + " screenWidthDp=" + mConfig.screenWidthDp ++ + " screenHeightDp=" + mConfig.screenHeightDp); ++ if (displayMetrics.widthPixels < displayMetrics.heightPixels) ++ mConfig.densityDpi = (int)(mConfig.densityDpi / 412.0 * displayMetrics.widthPixels / displayMetrics.density); ++ else ++ mConfig.densityDpi = (int)(mConfig.densityDpi / 412.0 * displayMetrics.heightPixels / displayMetrics.density); ++ displayMetrics.densityDpi = mConfig.densityDpi; ++ Log.i("PixelPerfect","Setting new densityDpi to " + mConfig.densityDpi); ++ getResources().updateConfiguration(mConfig, displayMetrics); ++ } + // Make sure the root coordinator is created prior to calling super to ensure all + // the activity lifecycle events are called. + mRootUiCoordinator = createRootUiCoordinator(); +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java +@@ -925,7 +925,7 @@ public class CompositorViewHolder extends FrameLayout + int viewportInsets = controlsInsets + keyboardInset; + + if (isAttachedToWindow(view)) { +- webContents.setSize(width, height - viewportInsets); ++ webContents.setSize(width, height - viewportInsets, viewportInsets); + + // Dispatch the geometrychange JavaScript event to the page. + // TODO(bokan): This doesn't belong in updateWebContentsSize. Ideally the content/ layer +@@ -945,7 +945,7 @@ public class CompositorViewHolder extends FrameLayout + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); +- webContents.setSize(view.getWidth(), view.getHeight() - viewportInsets); ++ webContents.setSize(view.getWidth(), view.getHeight() - viewportInsets, viewportInsets); + requestRender(); + } + } +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java +@@ -610,7 +610,7 @@ public class OverlayPanelContent { + OverlayPanelContentJni.get() + .onPhysicalBackingSizeChanged( + mNativeOverlayPanelContentPtr, webContents, mContentViewWidth, viewHeight); +- mWebContents.setSize(mContentViewWidth, viewHeight); ++ mWebContents.setSize(mContentViewWidth, viewHeight, (mSubtractBarHeight ? mBarHeightPx : 0)); + } + + /** Destroy the native component of this class. */ +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java +@@ -1864,7 +1864,7 @@ class TabImpl implements Tab { + // so that next onShow() call won't send a resize message with zero size + // to the renderer process. This prevents the size fluttering that may confuse + // Blink and break rendered result (see http://crbug.com/340987). +- webContents.setSize(original.width(), original.height()); ++ webContents.setSize(original.width(), original.height(), 0); + + if (bounds != null) { + assert mNativeTabAndroid != 0; +@@ -2237,7 +2237,7 @@ class TabImpl implements Tab { + Supplier compositorViewHolderSupplier = + assumeNonNull(getActivity()).getCompositorViewHolderSupplier(); + View compositorView = compositorViewHolderSupplier.get(); +- webContents.setSize(compositorView.getWidth(), compositorView.getHeight()); ++ webContents.setSize(compositorView.getWidth(), compositorView.getHeight(), 0); + + mWebContentsState = null; + initWebContents(webContents); +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tracing/settings/DeveloperSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/tracing/settings/DeveloperSettings.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/tracing/settings/DeveloperSettings.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/tracing/settings/DeveloperSettings.java +@@ -25,12 +25,14 @@ import org.chromium.chrome.browser.settings.ChromeBaseSettingsFragment; + import org.chromium.components.browser_ui.settings.EmbeddableSettingsPage; + import org.chromium.components.browser_ui.settings.SettingsUtils; + import org.chromium.chrome.browser.util.PlatformUtil; ++import org.chromium.chrome.browser.flags.CromiteNativeUtils; + + /** Settings fragment containing preferences aimed at Chrome and web developers. */ + @NullMarked + public class DeveloperSettings extends ChromeBaseSettingsFragment implements EmbeddableSettingsPage { + private static final String UI_PREF_BETA_STABLE_HINT = "beta_stable_hint"; + private static final String TEST_GWP_ASAN_KEY = "test_gwp_asan"; ++ private static final String PIXEL_PERFECT_MODE_KEY = "pixel_perfect_mode"; + + // Non-translated strings: + private static final String MSG_DEVELOPER_OPTIONS_TITLE = "Developer options"; +@@ -82,6 +84,14 @@ public class DeveloperSettings extends ChromeBaseSettingsFragment implements Emb + // handle the click so the default action isn't triggered. + return true; + }); ++ ++ Preference mPixelPerfect = findPreference(PIXEL_PERFECT_MODE_KEY); ++ mPixelPerfect.setOnPreferenceChangeListener((preference, newValue) -> { ++ CromiteNativeUtils.setFlagEnabled("AndroidPixelPerfect", (boolean)newValue); ++ CromiteNativeUtils.setFlagEnabled("pixel-perfect-mode", (boolean)newValue); ++ getRequestRestartDelegateSupplier().get().RequireRestart(); ++ return true; ++ }); + } + + @Override +diff --git a/chrome/browser/android/bottombar/overlay_panel_content.cc b/chrome/browser/android/bottombar/overlay_panel_content.cc +--- a/chrome/browser/android/bottombar/overlay_panel_content.cc ++++ b/chrome/browser/android/bottombar/overlay_panel_content.cc +@@ -51,7 +51,7 @@ void OverlayPanelContent::OnPhysicalBackingSizeChanged( + jint height) { + gfx::Size size(width, height); + web_contents->GetNativeView()->OnPhysicalBackingSizeChanged(size); +- web_contents->GetNativeView()->OnSizeChanged(width, height); ++ web_contents->GetNativeView()->OnSizeChanged(width, height, width, height); + } + + void OverlayPanelContent::SetWebContents( +diff --git a/chrome/browser/android/compositor/tab_content_manager.cc b/chrome/browser/android/compositor/tab_content_manager.cc +--- a/chrome/browser/android/compositor/tab_content_manager.cc ++++ b/chrome/browser/android/compositor/tab_content_manager.cc +@@ -26,6 +26,7 @@ + #include "chrome/browser/android/compositor/layer/thumbnail_layer.h" + #include "chrome/browser/android/tab_android.h" + #include "chrome/browser/thumbnail/cc/thumbnail.h" ++#include "content/public/common/content_features.h" + #include "content/public/browser/browser_thread.h" + #include "content/public/browser/render_frame_host.h" + #include "content/public/browser/render_view_host.h" +@@ -74,7 +75,10 @@ class TabContentManager::TabReadbackRequest { + std::move(result_callback).Run(SkBitmap()); + return; + } +- gfx::Rect source_rect = gfx::Rect(view_size_in_pixels); ++ float scale = rwhv->GetNativeView()->GetDipScale(); ++ if (!base::FeatureList::IsEnabled(features::kPixelPerfect)) ++ scale = 1; ++ gfx::Rect source_rect = gfx::Rect(view_size_in_pixels.width() / scale, view_size_in_pixels.height() / scale); + gfx::Size thumbnail_size( + gfx::ScaleToCeiledSize(view_size_in_pixels, thumbnail_scale_)); + rwhv->CopyFromSurface(source_rect, thumbnail_size, +diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sPixelPerfect.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sPixelPerfect.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sPixelPerfect.java +@@ -0,0 +1,33 @@ ++/* ++ This file is part of Cromite. ++ ++ Cromite is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ Cromite is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with Cromite. If not, see . ++*/ ++ ++package org.chromium.chrome.browser.flags.cromite; ++ ++import org.chromium.components.cached_flags.CachedFlag; ++import org.chromium.chrome.browser.flags.ChromeFeatureMap; ++ ++public class sPixelPerfect { ++ private static final CachedFlag sInstance = ++ new CachedFlag(ChromeFeatureMap.getInstance(), ++ "AndroidPixelPerfect", false); ++ ++ private sPixelPerfect() {} ++ ++ public static CachedFlag getInstance() { ++ return sInstance; ++ } ++} +diff --git a/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/ChromeBaseSettingsFragment.java b/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/ChromeBaseSettingsFragment.java +--- a/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/ChromeBaseSettingsFragment.java ++++ b/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/ChromeBaseSettingsFragment.java +@@ -46,6 +46,10 @@ public abstract class ChromeBaseSettingsFragment extends PreferenceFragmentCompa + mRequireRestartDelegateSupplier = delegate; + } + ++ protected Supplier getRequestRestartDelegateSupplier() { ++ return mRequireRestartDelegateSupplier; ++ } ++ + public void onCreatePreferencesCromite(Bundle savedInstanceState, String rootKey) { + } + +diff --git a/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Pixel-perfect-mode.grdp b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Pixel-perfect-mode.grdp +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Pixel-perfect-mode.grdp +@@ -0,0 +1,9 @@ ++ ++ ++ ++ Pixel Perfect Mode ++ ++ ++ Eliminate conversions between dpi and pixels in Blink ++ ++ +diff --git a/components/fingerprinting_protection_filter/interventions/common/interventions_features.cc b/components/fingerprinting_protection_filter/interventions/common/interventions_features.cc +--- a/components/fingerprinting_protection_filter/interventions/common/interventions_features.cc ++++ b/components/fingerprinting_protection_filter/interventions/common/interventions_features.cc +@@ -19,6 +19,7 @@ BASE_FEATURE_PARAM(bool, + &kCanvasNoise, + "enable_in_regular_mode", + false); ++SET_CROMITE_FEATURE_DISABLED(kCanvasNoise); + + bool IsCanvasInterventionsEnabledForIncognitoState(bool is_incognito) { + if (is_incognito) { +diff --git a/components/input/android_input_helper.cc b/components/input/android_input_helper.cc +--- a/components/input/android_input_helper.cc ++++ b/components/input/android_input_helper.cc +@@ -132,7 +132,7 @@ void AndroidInputHelper::ProcessAckedTouchEvent( + } + } + +-bool AndroidInputHelper::TransformPointToCoordSpaceForView( ++bool AndroidInputHelper::TransformPointToCoordSpaceForView( // POI + const gfx::PointF& point, + input::RenderWidgetHostViewInput* target_view, + gfx::PointF* transformed_point) { +diff --git a/components/input/child_frame_input_helper.cc b/components/input/child_frame_input_helper.cc +--- a/components/input/child_frame_input_helper.cc ++++ b/components/input/child_frame_input_helper.cc +@@ -153,7 +153,7 @@ bool ChildFrameInputHelper::TransformPointToCoordSpaceForView( + point, target_view, view_->GetFrameSinkId(), transformed_point); + } + +-bool ChildFrameInputHelper::TransformPointToCoordSpaceForView( ++bool ChildFrameInputHelper::TransformPointToCoordSpaceForView( // POI + const gfx::PointF& point, + input::RenderWidgetHostViewInput* target_view, + const viz::FrameSinkId& local_frame_sink_id, +diff --git a/components/input/input_router_impl.cc b/components/input/input_router_impl.cc +--- a/components/input/input_router_impl.cc ++++ b/components/input/input_router_impl.cc +@@ -652,7 +652,7 @@ bool InputRouterImpl::IsAutoscrollInProgress() { + return client_->IsAutoscrollInProgress(); + } + +-void InputRouterImpl::FilterAndSendWebInputEvent( ++void InputRouterImpl::FilterAndSendWebInputEvent( // POI + const WebInputEvent& input_event, + const ui::LatencyInfo& latency_info, + blink::mojom::WidgetInputHandler::DispatchEventCallback callback, +diff --git a/components/input/render_widget_host_input_event_router.cc b/components/input/render_widget_host_input_event_router.cc +--- a/components/input/render_widget_host_input_event_router.cc ++++ b/components/input/render_widget_host_input_event_router.cc +@@ -16,6 +16,7 @@ + #include "base/metrics/histogram_macros.h" + #include "base/strings/stringprintf.h" + #include "base/trace_event/trace_event.h" ++#include "content/public/common/content_features.h" + #include "components/input/cursor_manager.h" + #include "components/input/features.h" + #include "components/input/touch_emulator.h" +@@ -580,6 +581,8 @@ RenderWidgetTargetResult RenderWidgetHostInputEventRouter::FindViewAtLocation( + return {root_view, /*should_query_view=*/false, *transformed_point}; + } + float device_scale_factor = root_view->GetDeviceScaleFactor(); ++ if (base::FeatureList::IsEnabled(::features::kPixelPerfect)) ++ device_scale_factor = 1; + DCHECK_GT(device_scale_factor, 0.0f); + gfx::PointF point_in_pixels = + gfx::ConvertPointToPixels(point, device_scale_factor); +@@ -1647,7 +1650,7 @@ bool IsPinchCurrentlyAllowedInTarget(RenderWidgetHostViewInput* target) { + + } // namespace + +-void RenderWidgetHostInputEventRouter::DispatchTouchscreenGestureEvent( ++void RenderWidgetHostInputEventRouter::DispatchTouchscreenGestureEvent( // POI + RenderWidgetHostViewInput* root_view, + RenderWidgetHostViewInput* target, + const blink::WebGestureEvent& gesture_event, +diff --git a/components/input/render_widget_host_view_input.cc b/components/input/render_widget_host_view_input.cc +--- a/components/input/render_widget_host_view_input.cc ++++ b/components/input/render_widget_host_view_input.cc +@@ -5,6 +5,7 @@ + #include "components/input/render_widget_host_view_input.h" + + #include "base/notreached.h" ++#include "content/public/common/content_features.h" + #include "components/input/render_widget_host_input_event_router.h" + #include "ui/gfx/geometry/dip_util.h" + +@@ -328,6 +329,8 @@ bool RenderWidgetHostViewInput::TransformPointToTargetCoordSpace( + } + + float device_scale_factor = original_view->GetDeviceScaleFactor(); ++ if (base::FeatureList::IsEnabled(::features::kPixelPerfect)) ++ device_scale_factor = 1; + CHECK_GT(device_scale_factor, 0.0f); + // TODO(crbug.com/41460959): Optimize so that |point_in_pixels| doesn't need + // to be in the coordinate space of the root surface in HitTestQuery. +diff --git a/components/input/render_widget_targeter.cc b/components/input/render_widget_targeter.cc +--- a/components/input/render_widget_targeter.cc ++++ b/components/input/render_widget_targeter.cc +@@ -67,7 +67,7 @@ RenderWidgetTargetResult::RenderWidgetTargetResult( + + RenderWidgetTargetResult::~RenderWidgetTargetResult() = default; + +-RenderWidgetTargeter::TargetingRequest::TargetingRequest( ++RenderWidgetTargeter::TargetingRequest::TargetingRequest( // POI + base::WeakPtr root_view, + const blink::WebInputEvent& event, + const ui::LatencyInfo& latency) { +@@ -179,7 +179,7 @@ void RenderWidgetTargeter::FindTargetAndCallback( + ResolveTargetingRequest(std::move(request)); + } + +-void RenderWidgetTargeter::ResolveTargetingRequest(TargetingRequest request) { ++void RenderWidgetTargeter::ResolveTargetingRequest(TargetingRequest request) { // POI + if (request_in_flight_) { + requests_.push(std::move(request)); + return; +diff --git a/components/thin_webview/internal/thin_webview.cc b/components/thin_webview/internal/thin_webview.cc +--- a/components/thin_webview/internal/thin_webview.cc ++++ b/components/thin_webview/internal/thin_webview.cc +@@ -107,7 +107,7 @@ void ThinWebView::ResizeWebContents(const gfx::Size& size) { + return; + + web_contents_->GetNativeView()->OnPhysicalBackingSizeChanged(size); +- web_contents_->GetNativeView()->OnSizeChanged(size.width(), size.height()); ++ web_contents_->GetNativeView()->OnSizeChanged(size.width(), size.height(), size.width(), size.height()); + } + + } // namespace android +diff --git a/components/viz/service/input/input_manager.cc b/components/viz/service/input/input_manager.cc +--- a/components/viz/service/input/input_manager.cc ++++ b/components/viz/service/input/input_manager.cc +@@ -399,7 +399,7 @@ const DisplayHitTestQueryMap& InputManager::GetDisplayHitTestQuery() const { + return frame_sink_manager_->GetDisplayHitTestQuery(); + } + +-float InputManager::GetDeviceScaleFactorForId( ++float InputManager::GetDeviceScaleFactorForId( // POI + const FrameSinkId& frame_sink_id) { + auto* support = frame_sink_manager_->GetFrameSinkForId(frame_sink_id); + CHECK(support); +diff --git a/components/viz/service/input/render_input_router_support_android.cc b/components/viz/service/input/render_input_router_support_android.cc +--- a/components/viz/service/input/render_input_router_support_android.cc ++++ b/components/viz/service/input/render_input_router_support_android.cc +@@ -117,7 +117,7 @@ SurfaceId RenderInputRouterSupportAndroid::GetCurrentSurfaceId() const { + NOTREACHED(); + } + +-bool RenderInputRouterSupportAndroid::TransformPointToCoordSpaceForView( ++bool RenderInputRouterSupportAndroid::TransformPointToCoordSpaceForView( // POI + const gfx::PointF& point, + input::RenderWidgetHostViewInput* target_view, + gfx::PointF* transformed_point) { +diff --git a/components/viz/service/input/render_input_router_support_child_frame.cc b/components/viz/service/input/render_input_router_support_child_frame.cc +--- a/components/viz/service/input/render_input_router_support_child_frame.cc ++++ b/components/viz/service/input/render_input_router_support_child_frame.cc +@@ -86,7 +86,7 @@ gfx::PointF RenderInputRouterSupportChildFrame::TransformPointToRootCoordSpaceF( + return input_helper_->TransformPointToRootCoordSpaceF(point); + } + +-bool RenderInputRouterSupportChildFrame::TransformPointToCoordSpaceForView( ++bool RenderInputRouterSupportChildFrame::TransformPointToCoordSpaceForView( // POI + const gfx::PointF& point, + input::RenderWidgetHostViewInput* target_view, + gfx::PointF* transformed_point) { +diff --git a/components/viz/service/layers/layer_context_impl.cc b/components/viz/service/layers/layer_context_impl.cc +--- a/components/viz/service/layers/layer_context_impl.cc ++++ b/components/viz/service/layers/layer_context_impl.cc +@@ -1757,7 +1757,7 @@ base::expected LayerContextImpl::DoUpdateDisplayTree( + return base::unexpected("Invalid page scale factors"); + } + layers.SetPageScaleFactorAndLimitsForDisplayTree( +- update->page_scale_factor, update->min_page_scale_factor, ++ update->page_scale_factor, update->min_page_scale_factor, update->browser_control_scale_factor, + update->max_page_scale_factor); + + if (update->external_page_scale_factor <= 0 || +diff --git a/content/browser/android/selection/selection_popup_controller.cc b/content/browser/android/selection/selection_popup_controller.cc +--- a/content/browser/android/selection/selection_popup_controller.cc ++++ b/content/browser/android/selection/selection_popup_controller.cc +@@ -182,7 +182,7 @@ void SelectionPopupController::MoveRangeSelectionExtent( + web_contents_impl->MoveRangeSelectionExtent(gfx::ToRoundedPoint(extent)); + } + +-void SelectionPopupController::SelectBetweenCoordinates( ++void SelectionPopupController::SelectBetweenCoordinates( // POI + const gfx::PointF& base, + const gfx::PointF& extent) { + auto* web_contents_impl = static_cast(web_contents()); +@@ -303,7 +303,8 @@ bool SelectionPopupController::ShowSelectionMenu( + is_password_type, jselected_text, params.selection_start_offset, + can_select_all, can_edit_richly, should_suggest, + static_cast(params.source_type), +- render_frame_host->GetJavaRenderFrameHost()); ++ render_frame_host->GetJavaRenderFrameHost(), ++ base::FeatureList::IsEnabled(features::kPixelPerfect)); + return true; + } + +diff --git a/content/browser/renderer_host/cross_process_frame_connector.cc b/content/browser/renderer_host/cross_process_frame_connector.cc +--- a/content/browser/renderer_host/cross_process_frame_connector.cc ++++ b/content/browser/renderer_host/cross_process_frame_connector.cc +@@ -21,6 +21,7 @@ + #include "content/browser/renderer_host/render_widget_host_view_base.h" + #include "content/browser/renderer_host/render_widget_host_view_child_frame.h" + #include "content/common/features.h" ++#include "content/public/common/content_features.h" + #include "third_party/blink/public/common/frame/frame_visual_properties.h" + #include "third_party/blink/public/common/input/web_input_event.h" + #include "third_party/blink/public/mojom/frame/intrinsic_sizing_info.mojom.h" +@@ -210,9 +211,12 @@ void CrossProcessFrameConnector::SynchronizeVisualProperties( + render_widget_host->SetAutoResize(visual_properties.auto_resize_enabled, + visual_properties.min_size_for_auto_resize, + visual_properties.max_size_for_auto_resize); ++ auto compositing_scale_factor = visual_properties.compositing_scale_factor; ++ if (base::FeatureList::IsEnabled(features::kPixelPerfect)) ++ compositing_scale_factor = 4; + render_widget_host->SetVisualPropertiesFromParentFrame( + visual_properties.page_scale_factor, +- visual_properties.compositing_scale_factor, ++ compositing_scale_factor, + visual_properties.is_pinch_gesture_active, + visual_properties.visible_viewport_size, + visual_properties.compositor_viewport, +@@ -465,7 +469,7 @@ void CrossProcessFrameConnector::SetVisibilityForChildViews( + current_child_frame_host()->SetVisibilityForChildViews(visible); + } + +-void CrossProcessFrameConnector::SetLocalFrameSize( ++void CrossProcessFrameConnector::SetLocalFrameSize( // POI + const gfx::Size& local_frame_size) { + has_size_ = true; + const float dsf = screen_infos_.current().device_scale_factor; +@@ -474,7 +478,7 @@ void CrossProcessFrameConnector::SetLocalFrameSize( + gfx::ScaleToRoundedSize(local_frame_size, 1.f / dsf); + } + +-void CrossProcessFrameConnector::SetRectInParentView( ++void CrossProcessFrameConnector::SetRectInParentView( // POI + const gfx::Rect& rect_in_parent_view) { + gfx::Rect old_rect = rect_in_parent_view_in_dip_; + const float dsf = screen_infos_.current().device_scale_factor; +diff --git a/content/browser/renderer_host/delegated_frame_host.cc b/content/browser/renderer_host/delegated_frame_host.cc +--- a/content/browser/renderer_host/delegated_frame_host.cc ++++ b/content/browser/renderer_host/delegated_frame_host.cc +@@ -183,7 +183,7 @@ void DelegatedFrameHost::CopyFromCompositingSurfaceAsTexture( + std::move(callback)); + } + +-void DelegatedFrameHost::CopyFromCompositingSurfaceInternal( ++void DelegatedFrameHost::CopyFromCompositingSurfaceInternal( // POI + const gfx::Rect& src_subrect, + const gfx::Size& output_size, + const viz::SurfaceId& surface_id, +diff --git a/content/browser/renderer_host/input/touch_selection_controller_client_child_frame.cc b/content/browser/renderer_host/input/touch_selection_controller_client_child_frame.cc +--- a/content/browser/renderer_host/input/touch_selection_controller_client_child_frame.cc ++++ b/content/browser/renderer_host/input/touch_selection_controller_client_child_frame.cc +@@ -53,7 +53,7 @@ void TouchSelectionControllerClientChildFrame::OnHitTestRegionUpdated() { + } + + void TouchSelectionControllerClientChildFrame:: +- TransformSelectionBoundsAndUpdate() { ++ TransformSelectionBoundsAndUpdate() { // POI + gfx::SelectionBound transformed_selection_start(selection_start_); + gfx::SelectionBound transformed_selection_end(selection_end_); + +diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc +--- a/content/browser/renderer_host/render_frame_host_impl.cc ++++ b/content/browser/renderer_host/render_frame_host_impl.cc +@@ -9224,7 +9224,7 @@ void RenderFrameHostImpl::ShowPopupMenu( + #endif + } + +-void RenderFrameHostImpl::ShowContextMenu( ++void RenderFrameHostImpl::ShowContextMenu( // POI + mojo::PendingAssociatedRemote + context_menu_client, + const blink::UntrustworthyContextMenuParams& params) { +diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc +--- a/content/browser/renderer_host/render_widget_host_impl.cc ++++ b/content/browser/renderer_host/render_widget_host_impl.cc +@@ -1041,7 +1041,20 @@ blink::VisualProperties RenderWidgetHostImpl::GetVisualProperties() { + blink::VisualProperties visual_properties; + visual_properties.screen_infos = GetScreenInfos(); + auto& current_screen_info = visual_properties.screen_infos.mutable_current(); +- ++ float browser_controls_scale_factor = 1; ++ if (base::FeatureList::IsEnabled(features::kPixelPerfect)) { ++ browser_controls_scale_factor = current_screen_info.device_scale_factor_original; ++ if (browser_controls_scale_factor == 0) ++ browser_controls_scale_factor = current_screen_info.device_scale_factor; ++ if (browser_controls_scale_factor == 0) ++ browser_controls_scale_factor = 1; ++ current_screen_info.rect = gfx::Rect(view_->GetRequestedRendererSizeDevicePx()); ++ current_screen_info.available_rect = current_screen_info.rect; ++ current_screen_info.device_scale_factor_original = browser_controls_scale_factor; ++ current_screen_info.device_scale_factor = 1.f; ++ } else { ++ current_screen_info.device_scale_factor_original = current_screen_info.device_scale_factor; ++ } + // For testing, override the raster color profile. + // Note: this needs to be done here and not earlier in the pipeline because + // Mac uses the display color space to update an NSSurface and this setting +@@ -1146,6 +1159,13 @@ blink::VisualProperties RenderWidgetHostImpl::GetVisualProperties() { + visual_properties.compositing_scale_factor = + properties_from_parent_local_root_.compositing_scale_factor; + ++ visual_properties.browser_controls_scale_factor = 1; ++ if (is_topmost_widget) { ++ visual_properties.browser_controls_scale_factor = browser_controls_scale_factor; ++ visual_properties.compositor_viewport_pixel_rect_real = ++ gfx::Rect(view_->GetCompositorViewportRealPixelSize()); ++ } ++ + #if BUILDFLAG(IS_MAC) + // Only macOS cursor scaling affects CSS custom cursor images for now. + visual_properties.cursor_accessibility_scale_factor = +@@ -1326,8 +1346,10 @@ bool RenderWidgetHostImpl::SynchronizeVisualProperties( + } + } + +- GetRenderInputRouter()->SetDeviceScaleFactor( +- visual_properties->screen_infos.current().device_scale_factor); ++ auto device_scale_factor = visual_properties->screen_infos.current().device_scale_factor; ++ if (base::FeatureList::IsEnabled(features::kPixelPerfect)) ++ device_scale_factor = 2.f; ++ GetRenderInputRouter()->SetDeviceScaleFactor(device_scale_factor); + + // If we do not have a valid viz::LocalSurfaceId then we are a child frame + // waiting on the id to be propagated from our parent. We cannot create a hash +@@ -3958,7 +3980,7 @@ void RenderWidgetHostImpl::IntrinsicSizingInfoChanged( + // porting drag-and-drop calls to Mojo, so that RenderWidgetHostImpl bypasses + // RenderWidget to talk the the WebFrameWidget and needs to perform the scale + // operation itself. +-gfx::PointF RenderWidgetHostImpl::ConvertWindowPointToViewport( ++gfx::PointF RenderWidgetHostImpl::ConvertWindowPointToViewport( // POI + const gfx::PointF& window_point) { + gfx::PointF viewport_point = window_point; + viewport_point.Scale(GetScaleFactorForView(GetView())); +diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc +--- a/content/browser/renderer_host/render_widget_host_view_android.cc ++++ b/content/browser/renderer_host/render_widget_host_view_android.cc +@@ -82,6 +82,7 @@ + #include "content/public/browser/render_widget_host_iterator.h" + #include "content/public/common/content_client.h" + #include "content/public/common/content_switches.h" ++#include "content/public/common/content_features.h" + #include "third_party/blink/public/mojom/input/input_handler.mojom.h" + #include "third_party/skia/include/core/SkBitmap.h" + #include "third_party/skia/include/core/SkCanvas.h" +@@ -951,7 +952,7 @@ void RenderWidgetHostViewAndroid::OnRenderFrameMetadataChangedBeforeActivation( + ? SK_ColorTRANSPARENT + : metadata.root_background_color.toSkColor()); + +- if (overscroll_controller_) { ++ if (overscroll_controller_) { // POI + overscroll_controller_->OnFrameMetadataUpdated( + metadata.page_scale_factor, metadata.device_scale_factor, + metadata.scrollable_viewport_size, metadata.root_layer_size, +@@ -1262,6 +1263,12 @@ gfx::Size RenderWidgetHostViewAndroid::GetRequestedRendererSizeDevicePx() { + default_bounds_dip_.height() * scale_factor); + } + ++ if (base::FeatureList::IsEnabled(features::kPixelPerfect)) { ++ auto size = view_.GetSizeDIPs(); ++ // this is also the viewport width ++ DCHECK(size.width() == 0 || size.width() == 412 || size.height() == 412); ++ return size; ++ } + return view_.GetSizeDevicePx(); + } + +@@ -1286,6 +1293,10 @@ void RenderWidgetHostViewAndroid::SetInsets(const gfx::Insets& insets) { + NOTREACHED(); + } + ++gfx::Size RenderWidgetHostViewAndroid::GetCompositorViewportRealPixelSize() { ++ return view_.GetSizeReal(); ++} ++ + gfx::Size RenderWidgetHostViewAndroid::GetCompositorViewportPixelSize() { + if (!view_.parent()) { + if (default_bounds_dip_.IsEmpty()) { +@@ -1414,7 +1425,7 @@ viz::SurfaceId RenderWidgetHostViewAndroid::GetCurrentSurfaceId() const { + : viz::SurfaceId(); + } + +-bool RenderWidgetHostViewAndroid::TransformPointToCoordSpaceForView( ++bool RenderWidgetHostViewAndroid::TransformPointToCoordSpaceForView( // POI + const gfx::PointF& point, + RenderWidgetHostViewInput* target_view, + gfx::PointF* transformed_point) { +@@ -1798,7 +1809,7 @@ bool RenderWidgetHostViewAndroid::HasFallbackSurface() const { + return delegated_frame_host_ && delegated_frame_host_->HasFallbackSurface(); + } + +-void RenderWidgetHostViewAndroid::CopyFromSurface( ++void RenderWidgetHostViewAndroid::CopyFromSurface( // POI + const gfx::Rect& src_subrect, + const gfx::Size& output_size, + base::OnceCallback callback) { +diff --git a/content/browser/renderer_host/render_widget_host_view_android.h b/content/browser/renderer_host/render_widget_host_view_android.h +--- a/content/browser/renderer_host/render_widget_host_view_android.h ++++ b/content/browser/renderer_host/render_widget_host_view_android.h +@@ -159,6 +159,7 @@ class CONTENT_EXPORT RenderWidgetHostViewAndroid + gfx::Size GetRequestedRendererSizeDevicePx() override; + gfx::Size GetVisibleViewportSize() override; + gfx::Size GetVisibleViewportSizeDevicePx() override; ++ gfx::Size GetCompositorViewportRealPixelSize() override; + void SetInsets(const gfx::Insets& insets) override; + gfx::Size GetCompositorViewportPixelSize() override; + bool IsSurfaceAvailableForCopy() override; +diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc +--- a/content/browser/renderer_host/render_widget_host_view_base.cc ++++ b/content/browser/renderer_host/render_widget_host_view_base.cc +@@ -110,6 +110,10 @@ gfx::Size RenderWidgetHostViewBase::GetCompositorViewportPixelSize() { + GetDeviceScaleFactor()); + } + ++gfx::Size RenderWidgetHostViewBase::GetCompositorViewportRealPixelSize() { ++ return gfx::Size(); ++} ++ + void RenderWidgetHostViewBase::SelectionBoundsChanged( + const gfx::Rect& anchor_rect, + base::i18n::TextDirection anchor_dir, +@@ -581,7 +585,7 @@ display::ScreenInfos RenderWidgetHostViewBase::GetScreenInfos() const { + + void RenderWidgetHostViewBase::ResetGestureDetection() {} + +-float RenderWidgetHostViewBase::GetDeviceScaleFactor() const { ++float RenderWidgetHostViewBase::GetDeviceScaleFactor() const { // POI + return GetScreenInfos().current().device_scale_factor; + } + +diff --git a/content/browser/renderer_host/render_widget_host_view_base.h b/content/browser/renderer_host/render_widget_host_view_base.h +--- a/content/browser/renderer_host/render_widget_host_view_base.h ++++ b/content/browser/renderer_host/render_widget_host_view_base.h +@@ -274,6 +274,8 @@ class CONTENT_EXPORT RenderWidgetHostViewBase + // The size of the view's backing surface in non-DPI-adjusted pixels. + virtual gfx::Size GetCompositorViewportPixelSize(); + ++ virtual gfx::Size GetCompositorViewportRealPixelSize(); ++ + virtual gfx::AcceleratedWidget AccessibilityGetAcceleratedWidget(); + virtual gfx::NativeViewAccessible AccessibilityGetNativeViewAccessible(); + virtual gfx::NativeViewAccessible +diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame.cc b/content/browser/renderer_host/render_widget_host_view_child_frame.cc +--- a/content/browser/renderer_host/render_widget_host_view_child_frame.cc ++++ b/content/browser/renderer_host/render_widget_host_view_child_frame.cc +@@ -437,6 +437,10 @@ void RenderWidgetHostViewChildFrame::RequestInputBackForDragAndDrop( + } + #endif + ++gfx::Size RenderWidgetHostViewChildFrame::GetCompositorViewportRealPixelSize() { ++ return gfx::Size(); ++} ++ + RenderWidgetHostViewBase* RenderWidgetHostViewChildFrame::GetRootView() { + return frame_connector_ ? frame_connector_->GetRootRenderWidgetHostView() + : nullptr; +diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame.h b/content/browser/renderer_host/render_widget_host_view_child_frame.h +--- a/content/browser/renderer_host/render_widget_host_view_child_frame.h ++++ b/content/browser/renderer_host/render_widget_host_view_child_frame.h +@@ -117,6 +117,7 @@ class CONTENT_EXPORT RenderWidgetHostViewChildFrame + RenderWidgetHostViewBase* GetRootView() override; + uint32_t GetCaptureSequenceNumber() const override; + gfx::Size GetCompositorViewportPixelSize() override; ++ gfx::Size GetCompositorViewportRealPixelSize() override; + void InitAsPopup(RenderWidgetHostView* parent_host_view, + const gfx::Rect& bounds, + const gfx::Rect& anchor_rect) override; +diff --git a/content/browser/web_contents/web_contents_android.cc b/content/browser/web_contents/web_contents_android.cc +--- a/content/browser/web_contents/web_contents_android.cc ++++ b/content/browser/web_contents/web_contents_android.cc +@@ -822,8 +822,11 @@ WebContentsAndroid::GetFullscreenVideoSize(JNIEnv* env) { + return Java_WebContentsImpl_createSize(env, size.width(), size.height()); + } + +-void WebContentsAndroid::SetSize(JNIEnv* env, jint width, jint height) { +- web_contents_->GetNativeView()->OnSizeChanged(width, height); ++void WebContentsAndroid::SetSize(JNIEnv* env, jint width, jint height, jint viewport_insets) { ++ int fixed_width = width; ++ int fixed_height = height; ++ web_contents_->GetNativeView()->OnSizeChanged( ++ fixed_width, fixed_height, width, height); + } + + int WebContentsAndroid::GetWidth(JNIEnv* env) { +diff --git a/content/browser/web_contents/web_contents_android.h b/content/browser/web_contents/web_contents_android.h +--- a/content/browser/web_contents/web_contents_android.h ++++ b/content/browser/web_contents/web_contents_android.h +@@ -183,7 +183,7 @@ class CONTENT_EXPORT WebContentsAndroid { + + base::android::ScopedJavaLocalRef GetFullscreenVideoSize( + JNIEnv* env); +- void SetSize(JNIEnv* env, jint width, jint height); ++ void SetSize(JNIEnv* env, jint width, jint height, jint viewport_insets); + int GetWidth(JNIEnv* env); + int GetHeight(JNIEnv* env); + +diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc +--- a/content/child/runtime_features.cc ++++ b/content/child/runtime_features.cc +@@ -533,7 +533,11 @@ void SetCustomizedRuntimeFeaturesFromCombinedArgs( + !command_line.HasSwitch(switches::kFingerprintingCanvasImageDataNoise)); + // CAUTION: Only add custom enabling logic here if it cannot + // be covered by the other functions. +- ++ if (base::FeatureList::IsEnabled(features::kPixelPerfect)) { ++ WebRuntimeFeatures::EnableFingerprintingCanvasMeasureTextNoise(false); ++ WebRuntimeFeatures::EnableFingerprintingCanvasImageDataNoise(false); ++ WebRuntimeFeatures::EnablePixelPerfect(true); ++ } + // These checks are custom wrappers around base::FeatureList::IsEnabled + // They're moved here to distinguish them from actual base checks + #if !BUILDFLAG(IS_CHROMEOS) +diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java +--- a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java ++++ b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java +@@ -520,7 +520,8 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper + boolean canRichlyEdit, + boolean shouldSuggest, + int sourceType, +- RenderFrameHost renderFrameHost) { ++ RenderFrameHost renderFrameHost, ++ boolean pixelPerfectEnabled) { + RecordHistogram.recordEnumeratedHistogram( + "Android.ShowSelectionMenuSourceType", + sourceType, +@@ -531,6 +532,8 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper + mXDip = xDip; + mYDip = yDip; + mSelectionRect.set(left, top, right, offsetBottom); ++ if (pixelPerfectEnabled) ++ mSelectionRect.set(left, mYDip, right, offsetBottom); + mEditable = isEditable; + mLastSelectedText = selectionText; + mLastSelectionOffset = selectionStartOffset; +diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java +--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java ++++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java +@@ -977,9 +977,9 @@ public class WebContentsImpl + } + + @Override +- public void setSize(int width, int height) { ++ public void setSize(int width, int height, int viewportInsets) { + checkNotDestroyed(); +- WebContentsImplJni.get().setSize(mNativeWebContentsAndroid, width, height); ++ WebContentsImplJni.get().setSize(mNativeWebContentsAndroid, width, height, viewportInsets); + } + + @Override +@@ -1442,7 +1442,7 @@ public class WebContentsImpl + + Rect getFullscreenVideoSize(long nativeWebContentsAndroid); + +- void setSize(long nativeWebContentsAndroid, int width, int height); ++ void setSize(long nativeWebContentsAndroid, int width, int height, int viewportInsets); + + int getWidth(long nativeWebContentsAndroid); + +diff --git a/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java b/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java +--- a/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java ++++ b/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java +@@ -532,7 +532,7 @@ public interface WebContents extends Parcelable { + * @param width The width of the view. + * @param height The height of the view. + */ +- void setSize(int width, int height); ++ void setSize(int width, int height, int viewport_insets); + + /** + * Gets the view size width of the WebContents. +diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc +--- a/content/public/common/content_features.cc ++++ b/content/public/common/content_features.cc +@@ -17,6 +17,10 @@ + + namespace features { + ++CROMITE_FEATURE(kPixelPerfect, ++ "AndroidPixelPerfect", ++ base::FEATURE_DISABLED_BY_DEFAULT); ++ + // All features in alphabetical order. + + // Kill switch to guard additional security checks performed by the browser +diff --git a/cromite_flags/chrome/browser/about_flags_cc/Pixel-perfect-mode.inc b/cromite_flags/chrome/browser/about_flags_cc/Pixel-perfect-mode.inc +new file mode 100644 +--- /dev/null ++++ b/cromite_flags/chrome/browser/about_flags_cc/Pixel-perfect-mode.inc +@@ -0,0 +1,7 @@ ++#ifdef FLAG_SECTION ++ ++ {"pixel-perfect-mode", "Pixel Perfect Mode", ++ "Enable Pixel Perfect Mode", kOsAndroid, ++ FEATURE_VALUE_TYPE(features::kPixelPerfect)}, ++ ++#endif +diff --git a/cromite_flags/content/public/common/content_features_cc/Pixel-perfect-mode.inc b/cromite_flags/content/public/common/content_features_cc/Pixel-perfect-mode.inc +new file mode 100644 +--- /dev/null ++++ b/cromite_flags/content/public/common/content_features_cc/Pixel-perfect-mode.inc +@@ -0,0 +1 @@ ++// empty for now +diff --git a/cromite_flags/content/public/common/content_features_h/Pixel-perfect-mode.inc b/cromite_flags/content/public/common/content_features_h/Pixel-perfect-mode.inc +new file mode 100644 +--- /dev/null ++++ b/cromite_flags/content/public/common/content_features_h/Pixel-perfect-mode.inc +@@ -0,0 +1 @@ ++CONTENT_EXPORT BASE_DECLARE_FEATURE(kPixelPerfect); +diff --git a/services/viz/public/mojom/compositing/layer_context.mojom b/services/viz/public/mojom/compositing/layer_context.mojom +--- a/services/viz/public/mojom/compositing/layer_context.mojom ++++ b/services/viz/public/mojom/compositing/layer_context.mojom +@@ -74,6 +74,7 @@ struct LayerTreeUpdate { + // The page scale factors set by the tree's client. All must be positive, + // non-zero. + float page_scale_factor; ++ float browser_control_scale_factor; + float min_page_scale_factor; + float max_page_scale_factor; + float external_page_scale_factor; +diff --git a/third_party/blink/common/widget/visual_properties.cc b/third_party/blink/common/widget/visual_properties.cc +--- a/third_party/blink/common/widget/visual_properties.cc ++++ b/third_party/blink/common/widget/visual_properties.cc +@@ -22,6 +22,10 @@ bool VisualProperties::operator==(const VisualProperties& other) const { + new_size_device_px == other.new_size_device_px && + visible_viewport_size_device_px == + other.visible_viewport_size_device_px && ++ compositor_viewport_pixel_rect_real == ++ other.compositor_viewport_pixel_rect_real && ++ browser_controls_scale_factor == ++ other.browser_controls_scale_factor && + compositor_viewport_pixel_rect == + other.compositor_viewport_pixel_rect && + browser_controls_params == other.browser_controls_params && +diff --git a/third_party/blink/common/widget/visual_properties_mojom_traits.cc b/third_party/blink/common/widget/visual_properties_mojom_traits.cc +--- a/third_party/blink/common/widget/visual_properties_mojom_traits.cc ++++ b/third_party/blink/common/widget/visual_properties_mojom_traits.cc +@@ -21,6 +21,8 @@ bool StructTraits< + !data.ReadNewSizeDevicePx(&out->new_size_device_px) || + !data.ReadVisibleViewportSizeDevicePx( + &out->visible_viewport_size_device_px) || ++ !data.ReadCompositorViewportPixelRectReal( ++ &out->compositor_viewport_pixel_rect_real) || + !data.ReadCompositorViewportPixelRect( + &out->compositor_viewport_pixel_rect) || + !data.ReadBrowserControlsParams(&out->browser_controls_params) || +@@ -39,6 +41,7 @@ bool StructTraits< + out->is_fullscreen_granted = data.is_fullscreen_granted(); + out->display_mode = data.display_mode(); + out->capture_sequence_number = data.capture_sequence_number(); ++ out->browser_controls_scale_factor = data.browser_controls_scale_factor(); + out->zoom_level = data.zoom_level(); + out->css_zoom_factor = data.css_zoom_factor(); + out->page_scale_factor = data.page_scale_factor(); +diff --git a/third_party/blink/public/common/widget/visual_properties.h b/third_party/blink/public/common/widget/visual_properties.h +--- a/third_party/blink/public/common/widget/visual_properties.h ++++ b/third_party/blink/public/common/widget/visual_properties.h +@@ -75,6 +75,10 @@ struct BLINK_COMMON_EXPORT VisualProperties { + // the widget, such as with an on-screen keyboard. + gfx::Size visible_viewport_size_device_px; + ++ gfx::Rect compositor_viewport_pixel_rect_real; ++ ++ float browser_controls_scale_factor = 1.f; ++ + // The rect of compositor's viewport in device pixels. Note that for top level + // widgets this is the same as |new_size| (when UseDevicePixelsForWidgetSizing + // is on; otherwise different by device pixel ratio) except that on Android +diff --git a/third_party/blink/public/common/widget/visual_properties_mojom_traits.h b/third_party/blink/public/common/widget/visual_properties_mojom_traits.h +--- a/third_party/blink/public/common/widget/visual_properties_mojom_traits.h ++++ b/third_party/blink/public/common/widget/visual_properties_mojom_traits.h +@@ -50,6 +50,17 @@ struct BLINK_COMMON_EXPORT StructTraits& snapshot, + ExecutionContext* execution_context) { ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) return false; + CanvasNoiseReason noise_reason = CanvasNoiseReason::kAllConditionsMet; + if (!rendering_context) { + noise_reason |= CanvasNoiseReason::kNoRenderContext; +diff --git a/third_party/blink/renderer/core/css/media_values.cc b/third_party/blink/renderer/core/css/media_values.cc +--- a/third_party/blink/renderer/core/css/media_values.cc ++++ b/third_party/blink/renderer/core/css/media_values.cc +@@ -164,7 +164,7 @@ double MediaValues::CalculateDynamicViewportHeight(LocalFrame* frame) { + return frame->View()->DynamicViewportSizeForViewportUnits().height(); + } + +-int MediaValues::CalculateDeviceWidth(LocalFrame* frame) { ++int MediaValues::CalculateDeviceWidth(LocalFrame* frame) { // POI + DCHECK(frame && frame->View() && frame->GetSettings() && frame->GetPage()); + + if (frame->DomWindow() && +@@ -182,7 +182,7 @@ int MediaValues::CalculateDeviceWidth(LocalFrame* frame) { + return device_width; + } + +-int MediaValues::CalculateDeviceHeight(LocalFrame* frame) { ++int MediaValues::CalculateDeviceHeight(LocalFrame* frame) { // POI + DCHECK(frame && frame->View() && frame->GetSettings() && frame->GetPage()); + + if (frame->DomWindow() && +diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc +--- a/third_party/blink/renderer/core/exported/web_view_impl.cc ++++ b/third_party/blink/renderer/core/exported/web_view_impl.cc +@@ -1247,8 +1247,19 @@ void WebViewImpl::UpdateICBAndResizeViewport( + if (GetBrowserControls().PermittedState() == + cc::BrowserControlsState::kBoth && + !GetBrowserControls().ShrinkViewport()) { +- icb_size.Enlarge(0, -(GetBrowserControls().TotalHeight() - +- GetBrowserControls().TotalMinHeight())); ++ int bc = -(GetBrowserControls().TotalHeight() - ++ GetBrowserControls().TotalMinHeight()); ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) { ++ bc = std::floor(bc / BrowserControlScaleFactor()); ++ bc = bc / 2 * 2; ++ if (bc >= -60 && bc <= -50) bc = -56; ++ if (bc >= -110 && bc <= -90) bc = -104; ++ // the browser controls size must be 56 (only bc) or 104 (bc + tab strip) ++ // (TODO) this is a fingerprint value that must be removed (also in chromium), ++ // tab strip must always be visible ++ DCHECK(bc == -56 || bc == -104); ++ } ++ icb_size.Enlarge(0, bc); + } + + GetPageScaleConstraintsSet().DidChangeInitialContainingBlockSize(icb_size); +@@ -1328,7 +1339,8 @@ void WebViewImpl::DidUpdateBrowserControls() { + ResizeViewportAnchor::ResizeScope resize_scope(*resize_viewport_anchor_); + + visual_viewport.SetBrowserControlsAdjustment( +- GetBrowserControls().UnreportedSizeAdjustment()); ++ GetBrowserControls().UnreportedSizeAdjustment(), ++ BrowserControlScaleFactor()); + } + } + +@@ -1392,7 +1404,7 @@ void WebViewImpl::ResizeWithBrowserControls( + float bottom_controls_height, + bool browser_controls_shrink_layout) { + ResizeWithBrowserControls( +- new_size, new_size, ++ new_size, new_size, gfx::Size(), browser_controls_scale_factor_, + {top_controls_height, GetBrowserControls().TopMinHeight(), + bottom_controls_height, GetBrowserControls().BottomMinHeight(), + GetBrowserControls().AnimateHeightChanges(), +@@ -1402,6 +1414,8 @@ void WebViewImpl::ResizeWithBrowserControls( + void WebViewImpl::ResizeWithBrowserControls( + const gfx::Size& main_frame_widget_size, + const gfx::Size& visible_viewport_size, ++ const gfx::Size& compositor_viewport_pixel_rect_real, ++ float browser_controls_scale_factor, + cc::BrowserControlsParams browser_controls_params) { + if (should_auto_resize_) { + // When auto-resizing only the viewport size comes from the browser, while +@@ -1410,6 +1424,11 @@ void WebViewImpl::ResizeWithBrowserControls( + return; + } + ++ if (GetPage()->MainFrame()) { ++ browser_controls_scale_factor_ = browser_controls_scale_factor; ++ GetPage()->GetVisualViewport().SetSizeReal(compositor_viewport_pixel_rect_real); ++ } ++ + if (size_ == main_frame_widget_size && + GetPage()->GetVisualViewport().Size() == visible_viewport_size && + GetBrowserControls().Params() == browser_controls_params) +@@ -2098,7 +2117,7 @@ void WebViewImpl::DidAttachLocalMainFrame() { + web_preferences_.prefers_reduced_motion); + MainFrameImpl()->FrameWidgetImpl()->SetPageScaleStateAndLimits( + viewport.Scale(), viewport.IsPinchGestureActive(), +- MinimumPageScaleFactor(), MaximumPageScaleFactor()); ++ MinimumPageScaleFactor(), BrowserControlScaleFactor(), MaximumPageScaleFactor()); + // Prevent main frame updates while the main frame is loading until enough + // progress is made and BeginMainFrames are explicitly asked for. + scoped_defer_main_frame_update_ = +@@ -2903,7 +2922,7 @@ void WebViewImpl::RefreshPageScaleFactor() { + auto& viewport = GetPage()->GetVisualViewport(); + MainFrameImpl()->FrameWidgetImpl()->SetPageScaleStateAndLimits( + viewport.Scale(), viewport.IsPinchGestureActive(), +- MinimumPageScaleFactor(), MaximumPageScaleFactor()); ++ MinimumPageScaleFactor(), BrowserControlScaleFactor(), MaximumPageScaleFactor()); + } + } + +@@ -2980,7 +2999,7 @@ void WebViewImpl::UpdatePageDefinedViewportConstraints( + TextAutosizer::UpdatePageInfoInAllFrames(GetPage()->MainFrame()); + } + +-void WebViewImpl::UpdateMainFrameLayoutSize() { ++void WebViewImpl::UpdateMainFrameLayoutSize() { // POI + if (should_auto_resize_ || !MainFrameImpl()) + return; + +@@ -3214,6 +3233,10 @@ float WebViewImpl::MinimumPageScaleFactor() const { + return GetPageScaleConstraintsSet().FinalConstraints().minimum_scale; + } + ++float WebViewImpl::BrowserControlScaleFactor() const { ++ return browser_controls_scale_factor_; ++} ++ + float WebViewImpl::MaximumPageScaleFactor() const { + return GetPageScaleConstraintsSet().FinalConstraints().maximum_scale; + } +@@ -3263,7 +3286,7 @@ void WebViewImpl::SendResizeEventForMainFrame() { + auto& viewport = GetPage()->GetVisualViewport(); + MainFrameImpl()->FrameWidgetImpl()->SetPageScaleStateAndLimits( + viewport.Scale(), viewport.IsPinchGestureActive(), +- MinimumPageScaleFactor(), MaximumPageScaleFactor()); ++ MinimumPageScaleFactor(), BrowserControlScaleFactor(), MaximumPageScaleFactor()); + } + } + +@@ -3799,7 +3822,7 @@ void WebViewImpl::PageScaleFactorChanged() { + DCHECK(viewport.IsActiveViewport()); + MainFrameImpl()->FrameWidgetImpl()->SetPageScaleStateAndLimits( + viewport.Scale(), viewport.IsPinchGestureActive(), +- MinimumPageScaleFactor(), MaximumPageScaleFactor()); ++ MinimumPageScaleFactor(), BrowserControlScaleFactor(), MaximumPageScaleFactor()); + + local_main_frame_host_remote_->ScaleFactorChanged(viewport.Scale()); + +diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h +--- a/third_party/blink/renderer/core/exported/web_view_impl.h ++++ b/third_party/blink/renderer/core/exported/web_view_impl.h +@@ -168,6 +168,7 @@ class CORE_EXPORT WebViewImpl final : public WebView, + void AdvanceFocus(bool reverse) override; + float PageScaleFactor() const override; + float MinimumPageScaleFactor() const override; ++ float BrowserControlScaleFactor() const override; + float MaximumPageScaleFactor() const override; + void SetDefaultPageScaleLimits(float min_scale, float max_scale) override; + void SetInitialPageScaleOverride(float) override; +@@ -282,6 +283,8 @@ class CORE_EXPORT WebViewImpl final : public WebView, + // includes all browser controls params such as the min heights. + void ResizeWithBrowserControls(const gfx::Size& main_frame_widget_size, + const gfx::Size& visible_viewport_size, ++ const gfx::Size& compositor_viewport_pixel_rect_real, ++ float browser_controls_scale_factor, + cc::BrowserControlsParams); + + // Requests a page-scale animation based on the specified point/rect. +@@ -820,6 +823,8 @@ class CORE_EXPORT WebViewImpl final : public WebView, + // The upper bound on the size when auto-resizing. + gfx::Size max_auto_size_; + ++ float browser_controls_scale_factor_ = 1.f; ++ + // An object that can be used to manipulate m_page->settings() without linking + // against WebCore. This is lazily allocated the first time GetWebSettings() + // is called. +diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc +--- a/third_party/blink/renderer/core/frame/local_frame_view.cc ++++ b/third_party/blink/renderer/core/frame/local_frame_view.cc +@@ -1719,8 +1719,11 @@ void LocalFrameView::PerformPostLayoutTasks(bool visual_viewport_size_changed) { + } + + float LocalFrameView::InputEventsScaleFactor() const { ++ float scale_factor = 1; ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) ++ scale_factor = 2; + float page_scale = frame_->GetPage()->GetVisualViewport().Scale(); +- return page_scale * ++ return page_scale * scale_factor * + frame_->GetPage()->GetChromeClient().InputEventsScaleForEmulation(); + } + +diff --git a/third_party/blink/renderer/core/frame/visual_viewport.cc b/third_party/blink/renderer/core/frame/visual_viewport.cc +--- a/third_party/blink/renderer/core/frame/visual_viewport.cc ++++ b/third_party/blink/renderer/core/frame/visual_viewport.cc +@@ -41,6 +41,7 @@ + #include "third_party/blink/public/platform/platform.h" + #include "third_party/blink/public/platform/task_type.h" + #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" ++#include "third_party/blink/renderer/core/frame/browser_controls.h" + #include "third_party/blink/renderer/core/frame/local_frame.h" + #include "third_party/blink/renderer/core/frame/local_frame_client.h" + #include "third_party/blink/renderer/core/frame/local_frame_view.h" +@@ -96,6 +97,7 @@ VisualViewport::VisualViewport(Page& owner) + scale_(1), + is_pinch_gesture_active_(false), + browser_controls_adjustment_(0), ++ browser_controls_scale_(0), + needs_paint_property_update_(true), + overscroll_type_(ComputeOverscrollType()) { + UniqueObjectId unique_id = NewUniqueObjectId(); +@@ -391,6 +393,30 @@ void VisualViewport::EnqueueResizeEvent() { + document->EnqueueVisualViewportResizeEvent(); + } + ++void VisualViewport::SetSizeReal(const gfx::Size& size) { ++ auto scale = ScaleFromDIP(); ++ auto size_dpi = gfx::Size(size.width() * scale, size.height() * scale); ++ if (size_real_ == size_dpi) ++ return; ++ ++ size_real_ = size_dpi; ++ ++ if (!IsActiveViewport()) ++ return; ++ ++ needs_paint_property_update_ = true; ++ ++ // Need to re-compute sizes for the overlay scrollbars. ++ if (scrollbar_layer_horizontal_ && LocalMainFrame().View()) { ++ DCHECK(scrollbar_layer_vertical_); ++ UpdateScrollbarLayer(kHorizontalScrollbar); ++ UpdateScrollbarLayer(kVerticalScrollbar); ++ LocalMainFrame().View()->SetVisualViewportOrOverlayNeedsRepaint(); ++ } ++ ++ EnqueueResizeEvent(); ++} ++ + void VisualViewport::SetSize(const gfx::Size& size) { + if (size_ == size) + return; +@@ -446,7 +472,8 @@ gfx::RectF VisualViewport::VisibleRect( + if (scrollbar_inclusion == kExcludeScrollbars) + visible_size = gfx::SizeF(ExcludeScrollbars(size_)); + +- visible_size.Enlarge(0, browser_controls_adjustment_); ++ int value = browser_controls_adjustment_ / browser_controls_scale_ * ScaleFromDIP(); ++ visible_size.Enlarge(0, value); + visible_size.Scale(1 / scale_); + + return gfx::RectF(ScrollPosition(), visible_size); +@@ -879,10 +906,13 @@ gfx::Point VisualViewport::ClampDocumentOffsetAtScale(const gfx::Point& offset, + return clamped; + } + +-void VisualViewport::SetBrowserControlsAdjustment(float adjustment) { ++void VisualViewport::SetBrowserControlsAdjustment(float adjustment, float browser_controls_scale) { + DCHECK(IsActiveViewport()); + DCHECK(LocalMainFrame().IsOutermostMainFrame()); + ++ browser_controls_scale_ = browser_controls_scale; ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) ++ adjustment = ceilf(adjustment / browser_controls_scale); + if (browser_controls_adjustment_ == adjustment) + return; + +diff --git a/third_party/blink/renderer/core/frame/visual_viewport.h b/third_party/blink/renderer/core/frame/visual_viewport.h +--- a/third_party/blink/renderer/core/frame/visual_viewport.h ++++ b/third_party/blink/renderer/core/frame/visual_viewport.h +@@ -123,7 +123,9 @@ class CORE_EXPORT VisualViewport : public GarbageCollected, + // The size of the Blink viewport area. See size_ for precise + // definition. + void SetSize(const gfx::Size&); ++ void SetSizeReal(const gfx::Size&); + gfx::Size Size() const { return size_; } ++ gfx::Size SizeReal() const { return size_real_; } + + // The area of the layout viewport rect visible in the visual viewport, + // relative to the layout viewport's top-left corner. i.e. As the page scale +@@ -165,7 +167,7 @@ class CORE_EXPORT VisualViewport : public GarbageCollected, + // scrollability aspect from LocalFrameView, we use this method to let + // VisualViewport make the necessary adjustments so that we don't incorrectly + // clamp scroll offsets coming from the compositor. crbug.com/422328 +- void SetBrowserControlsAdjustment(float); ++ void SetBrowserControlsAdjustment(float, float browser_controls_scale); + float BrowserControlsAdjustment() const; + + // Adjust the viewport's offset so that it remains bounded by the outer +@@ -187,6 +189,7 @@ class CORE_EXPORT VisualViewport : public GarbageCollected, + gfx::Point RootFrameToViewport(const gfx::Point&) const; + + // ScrollableArea implementation ++ //ScrollOffset ClampScrollOffset(const ScrollOffset&) const override; + ChromeClient* GetChromeClient() const override; + bool SetScrollOffset(const ScrollOffset&, + mojom::blink::ScrollType, +@@ -383,12 +386,14 @@ class CORE_EXPORT VisualViewport : public GarbageCollected, + // the changed visible content area. The transient URL bar-caused change to + // the visible content area is tracked in browser_controls_adjustment. + gfx::Size size_; ++ gfx::Size size_real_; + + // Blink is only resized as a result of showing/hiding the URL bar once + // they're fully committed (all the way hidden or shown). While they're + // animating or being dragged, browser_controls_adjustment_ tracks the amount + // they expand or shrink the visible content height. + float browser_controls_adjustment_; ++ float browser_controls_scale_; + + // For page scale animation on page_scale_node_. + CompositorElementId page_scale_element_id_; +diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc +--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc ++++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc +@@ -1954,6 +1954,9 @@ void WebFrameWidgetImpl::UpdateVisualProperties( + // All non-top-level Widgets (child local-root frames, GuestViews, + // etc.) propagate and consume the page scale factor as "external", meaning + // that it comes from the top level widget's page scale. ++ float compositing_scale_factor = visual_properties.compositing_scale_factor; ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) ++ compositing_scale_factor = 2.f; + if (!ForTopMostMainFrame()) { + // The main frame controls the page scale factor, from blink. For other + // frame widgets, the page scale from pinch zoom and compositing scale is +@@ -1961,7 +1964,7 @@ void WebFrameWidgetImpl::UpdateVisualProperties( + // blink doesn't need to know this page scale factor outside the main frame, + // the compositor does in order to produce its output at the correct scale. + float combined_scale_factor = visual_properties.page_scale_factor * +- visual_properties.compositing_scale_factor; ++ compositing_scale_factor; + widget_base_->LayerTreeHost()->SetExternalPageScaleFactor( + combined_scale_factor, visual_properties.is_pinch_gesture_active); + +@@ -1974,7 +1977,7 @@ void WebFrameWidgetImpl::UpdateVisualProperties( + // Ensure the external scale factor in top-level widgets is reset as it may + // be leftover from when a widget was nested and was promoted to top level. + widget_base_->LayerTreeHost()->SetExternalPageScaleFactor( +- 1.f, ++ compositing_scale_factor, + /*is_pinch_gesture_active=*/false); + } + +@@ -2018,6 +2021,18 @@ void WebFrameWidgetImpl::ApplyVisualPropertiesSizing( + new_compositor_viewport_pixel_rect, + device_scale_factor_for_testing_ / non_testing_device_scale_factor_); + } ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled() && ForMainFrame()) { ++ int fixed_width = visual_properties.new_size_device_px.width(); ++ int fixed_height = visual_properties.new_size_device_px.height(); ++ if (fixed_width <= fixed_height) ++ fixed_width = 412; ++ else ++ fixed_height = 412; ++ new_size = gfx::Size(fixed_width, fixed_height); ++ ++ new_compositor_viewport_pixel_rect = ++ gfx::Rect(0, 0, fixed_width * 2, fixed_height * 2); ++ } + + if (ForMainFrame()) { + if (size_ != new_size) { +@@ -2034,6 +2049,8 @@ void WebFrameWidgetImpl::ApplyVisualPropertiesSizing( + size_.value(), + widget_base_->DIPsToCeiledBlinkSpace( + widget_base_->VisibleViewportSize()), ++ visual_properties.compositor_viewport_pixel_rect_real.size(), ++ visual_properties.browser_controls_scale_factor, + visual_properties.browser_controls_params); + return; + } +@@ -2067,6 +2084,8 @@ void WebFrameWidgetImpl::ApplyVisualPropertiesSizing( + + View()->ResizeWithBrowserControls( + size_.value(), widget_base_->VisibleViewportSize(), ++ visual_properties.compositor_viewport_pixel_rect_real.size(), ++ visual_properties.browser_controls_scale_factor, + visual_properties.browser_controls_params); + } + +@@ -2442,6 +2461,8 @@ void WebFrameWidgetImpl::SetZoomInternal(double zoom_level, + View()->ZoomFactorForViewportLayout() * + static_cast(View()->ZoomLevelToZoomFactor(zoom_level)) * + static_cast(css_zoom_factor); ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) ++ DCHECK(layout_zoom_factor == 1.f); + if (zoom_changed) { + // Set the layout shift exclusion window for the zoom level change. + if (LocalFrameView* view = document->View()) { +@@ -3491,6 +3512,7 @@ void WebFrameWidgetImpl::SetRootLayer(scoped_refptr layer) { + // be needed in setting the raster scale. + SetPageScaleStateAndLimits(1.f, false /* is_pinch_gesture_active */, + View()->MinimumPageScaleFactor(), ++ View()->BrowserControlScaleFactor(), + View()->MaximumPageScaleFactor()); + } + +@@ -4860,9 +4882,10 @@ void WebFrameWidgetImpl::SetPageScaleStateAndLimits( + float page_scale_factor, + bool is_pinch_gesture_active, + float minimum, ++ float browser_control_scale_factor, + float maximum) { + widget_base_->LayerTreeHost()->SetPageScaleFactorAndLimits(page_scale_factor, +- minimum, maximum); ++ minimum, browser_control_scale_factor, maximum); + + // Only propagate page scale from the main frame. + if (ForMainFrame()) { +@@ -4986,7 +5009,10 @@ void WebFrameWidgetImpl::OrientationChanged() { + void WebFrameWidgetImpl::DidUpdateSurfaceAndScreen( + const display::ScreenInfos& previous_original_screen_infos) { + display::ScreenInfo screen_info = widget_base_->GetScreenInfo(); +- View()->SetZoomFactorForDeviceScaleFactor(screen_info.device_scale_factor); ++ float device_scale_factor = screen_info.device_scale_factor; ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) ++ device_scale_factor = 1; ++ View()->SetZoomFactorForDeviceScaleFactor(device_scale_factor); + + if (ShouldAutoDetermineCompositingToLCDTextSetting()) { + // This causes compositing state to be modified which dirties the +@@ -5182,7 +5208,7 @@ void WebFrameWidgetImpl::SetDeviceScaleFactorForTesting(float factor) { + + if (!device_scale_factor_for_testing_) { + non_testing_device_scale_factor_ = +- widget_base_->GetOriginalDeviceScaleFactor(); ++ widget_base_->GetOriginalDeviceScaleFactor(false); + } + + // Stash the window size before we adjust the scale factor, as subsequent +diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h +--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h ++++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h +@@ -656,6 +656,7 @@ class CORE_EXPORT WebFrameWidgetImpl + void SetPageScaleStateAndLimits(float page_scale_factor, + bool is_pinch_gesture_active, + float minimum, ++ float browser_control_scale_factor, + float maximum); + void UpdateViewportDescription( + const ViewportDescription& viewport_description); +diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc +--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc ++++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc +@@ -2543,7 +2543,7 @@ void WebLocalFrameImpl::PaintDevToolsOverlays(GraphicsContext& context) { + dev_tools_agent_->PaintOverlays(context); + } + +-void WebLocalFrameImpl::CreateFrameView() { ++void WebLocalFrameImpl::CreateFrameView() { // POI + TRACE_EVENT0("blink", "WebLocalFrameImpl::createFrameView"); + + DCHECK(GetFrame()); // If frame() doesn't exist, we probably didn't init +@@ -3018,7 +3018,7 @@ void WebLocalFrameImpl::ShowContextMenuFromExternal( + std::move(context_menu_client), params); + } + +-void WebLocalFrameImpl::ShowContextMenu( ++void WebLocalFrameImpl::ShowContextMenu( // POI + mojo::PendingAssociatedRemote client, + const blink::ContextMenuData& data, + const std::optional& host_context_menu_location) { +diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc +--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc ++++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc +@@ -1628,6 +1628,7 @@ bool HTMLCanvasElement::PushFrame(scoped_refptr&& image, + } + + bool HTMLCanvasElement::ShouldAccelerate() const { ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) return false; + if (context_ && !IsRenderingContext2D()) + return false; + +diff --git a/third_party/blink/renderer/core/input/event_handler.cc b/third_party/blink/renderer/core/input/event_handler.cc +--- a/third_party/blink/renderer/core/input/event_handler.cc ++++ b/third_party/blink/renderer/core/input/event_handler.cc +@@ -2082,7 +2082,7 @@ GestureEventWithHitTestResults EventHandler::HitTestResultForGestureEvent( + hit_test_result); + } + +-void EventHandler::ApplyTouchAdjustment(WebGestureEvent* gesture_event, ++void EventHandler::ApplyTouchAdjustment(WebGestureEvent* gesture_event, // POI + HitTestLocation& location, + HitTestResult& hit_test_result) { + TouchAdjustmentCandidateType touch_adjustment_candiate_type = +diff --git a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc +--- a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc ++++ b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc +@@ -1038,7 +1038,8 @@ InspectorEmulationAgent::setSmallViewportHeightDifferenceOverride( + // Ensure the override is applied immediately without having to wait for + // `WebFrameWidgetImpl::UpdateVisualProperties()` to be called. + GetWebViewImpl()->ResizeWithBrowserControls( +- GetWebViewImpl()->Size(), viewport_size, browser_controls_params); ++ GetWebViewImpl()->Size(), viewport_size, ++ gfx::Size(), GetWebViewImpl()->BrowserControlScaleFactor(), browser_controls_params); + + return protocol::Response::Success(); + } +diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc +--- a/third_party/blink/renderer/core/page/page.cc ++++ b/third_party/blink/renderer/core/page/page.cc +@@ -1322,6 +1322,10 @@ void Page::CalculateEmulatedScreenSetting(LocalFrame* frame, bool force) { + if (SchemeRegistry::IsWebUIScheme(protocol) || disable_screen_emulated_) { + isEnabled = false; + } ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) { ++ isEnabled = false; ++ force = false; ++ } + if (isEnabled || force) { + // this is the maximum (and minimum) value which in percentage + // corresponds to +- 0.03% +diff --git a/third_party/blink/renderer/core/page/viewport_description.cc b/third_party/blink/renderer/core/page/viewport_description.cc +--- a/third_party/blink/renderer/core/page/viewport_description.cc ++++ b/third_party/blink/renderer/core/page/viewport_description.cc +@@ -87,7 +87,7 @@ float ViewportDescription::ResolveViewportLength( + NOTREACHED(); + } + +-PageScaleConstraints ViewportDescription::Resolve( ++PageScaleConstraints ViewportDescription::Resolve( // POI + const gfx::SizeF& initial_viewport_size, + const Length& legacy_fallback_width) const { + float result_width = kValueAuto; +diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc +--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc ++++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc +@@ -465,16 +465,17 @@ ImageData* BaseRenderingContext2D::getImageDataInternal( + } + } + ++ bool pixelPerfect = RuntimeEnabledFeatures::PixelPerfectEnabled(); + // The default behavior before the willReadFrequently feature existed: + // Accelerated canvases fall back to CPU when there is a readback. +- if (will_read_frequently_value == ++ if (pixelPerfect || will_read_frequently_value == + CanvasContextCreationAttributesCore::WillReadFrequently::kUndefined) { + // GetImageData is faster in Unaccelerated canvases. + // In Desynchronized canvas disabling the acceleration will break + // putImageData: crbug.com/1112060. + if (IsAccelerated() && !IsDesynchronized()) { + read_count_++; +- if (read_count_ >= kFallbackToCPUAfterReadbacks || ++ if (pixelPerfect || read_count_ >= kFallbackToCPUAfterReadbacks || + ShouldDisableAccelerationBecauseOfReadback()) { + DisableAcceleration(); + base::UmaHistogramEnumeration("Blink.Canvas.GPUFallbackToCPU", +diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 +--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 ++++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 +@@ -5910,6 +5910,10 @@ + // If enabled, the `getDisplayMedia()` family of APIs will ask for NV12 + // frames, which should trigger a zero-copy path in the tab capture code. + name: "ZeroCopyTabCapture", +- } ++ }, ++ { ++ name: "PixelPerfect", ++ public: true, ++ }, + ], + } +diff --git a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc +--- a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc ++++ b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc +@@ -37,6 +37,7 @@ + #include "third_party/blink/public/common/input/web_mouse_wheel_event.h" + #include "third_party/blink/public/common/input/web_pointer_event.h" + #include "third_party/blink/public/common/input/web_touch_event.h" ++#include "third_party/blink/renderer/platform/runtime_enabled_features.h" + #include "third_party/blink/renderer/platform/widget/input/compositor_thread_event_queue.h" + #include "third_party/blink/renderer/platform/widget/input/cursor_control_handler.h" + #include "third_party/blink/renderer/platform/widget/input/elastic_overscroll_controller.h" +@@ -56,6 +57,20 @@ namespace { + using ::perfetto::protos::pbzero::ChromeLatencyInfo2; + using ::perfetto::protos::pbzero::TrackEvent; + ++gfx::PointF ScalePoint(const gfx::PointF& point) { ++ gfx::PointF point_scaled = point; ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) ++ point_scaled.Scale(.5f); ++ return point_scaled; ++} ++ ++gfx::Point ScalePoint(const gfx::Point& point) { ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled()) ++ return gfx::Point(point.x() / 2, point.y() / 2); ++ else ++ return point; ++} ++ + cc::ScrollStateData CreateScrollStateDataForGesture( + const WebGestureEvent& event) { + cc::ScrollStateData scroll_state_data; +@@ -64,8 +79,8 @@ cc::ScrollStateData CreateScrollStateDataForGesture( + } + switch (event.GetType()) { + case WebInputEvent::Type::kGestureScrollBegin: +- scroll_state_data.position_x = event.PositionInWidget().x(); +- scroll_state_data.position_y = event.PositionInWidget().y(); ++ scroll_state_data.position_x = event.PositionInWidget().x() / 2.f; ++ scroll_state_data.position_y = event.PositionInWidget().y() / 2.f; + scroll_state_data.delta_x_hint = -event.data.scroll_begin.delta_x_hint; + scroll_state_data.delta_y_hint = -event.data.scroll_begin.delta_y_hint; + scroll_state_data.is_beginning = true; +@@ -831,7 +846,7 @@ InputHandlerProxy::RouteToTypeSpecificHandler( + const WebGestureEvent& gesture_event = + static_cast(event); + input_handler_->PinchGestureBegin( +- gfx::ToFlooredPoint(gesture_event.PositionInWidget()), ++ gfx::ToFlooredPoint(ScalePoint(gesture_event.PositionInWidget())), + GestureScrollInputType(gesture_event.SourceDevice())); + gesture_pinch_in_progress_ = true; + return DID_HANDLE; +@@ -843,7 +858,7 @@ InputHandlerProxy::RouteToTypeSpecificHandler( + const WebGestureEvent& gesture_event = + static_cast(event); + input_handler_->PinchGestureEnd( +- gfx::ToFlooredPoint(gesture_event.PositionInWidget())); ++ gfx::ToFlooredPoint(ScalePoint(gesture_event.PositionInWidget()))); + return DID_HANDLE; + } + +@@ -853,7 +868,7 @@ InputHandlerProxy::RouteToTypeSpecificHandler( + static_cast(event); + input_handler_->PinchGestureUpdate( + gesture_event.data.pinch_update.scale, +- gfx::ToFlooredPoint(gesture_event.PositionInWidget())); ++ gfx::ToFlooredPoint(ScalePoint(gesture_event.PositionInWidget()))); + return DID_HANDLE; + } + +@@ -876,7 +891,7 @@ InputHandlerProxy::RouteToTypeSpecificHandler( + // TODO(arakeri): Pass in the modifier instead of a bool once the + // refactor (crbug.com/1022097) is done. For details, see + // crbug.com/1016955. +- HandlePointerDown(event_with_callback, mouse_event.PositionInWidget()); ++ HandlePointerDown(event_with_callback, ScalePoint(mouse_event.PositionInWidget())); + } + + return DID_NOT_HANDLE; +@@ -887,7 +902,7 @@ InputHandlerProxy::RouteToTypeSpecificHandler( + static_cast(event); + CHECK(input_handler_); + if (mouse_event.button == WebMouseEvent::Button::kLeft) +- HandlePointerUp(event_with_callback, mouse_event.PositionInWidget()); ++ HandlePointerUp(event_with_callback, ScalePoint(mouse_event.PositionInWidget())); + return DID_NOT_HANDLE; + } + case WebInputEvent::Type::kMouseMove: { +@@ -902,7 +917,7 @@ InputHandlerProxy::RouteToTypeSpecificHandler( + (mouse_event.button == WebPointerProperties::Button::kNoButton && + !(mouse_event.GetModifiers() & + WebInputEvent::Modifiers::kRelativeMotionEvent)); +- HandlePointerMove(event_with_callback, mouse_event.PositionInWidget(), ++ HandlePointerMove(event_with_callback, ScalePoint(mouse_event.PositionInWidget()), + should_cancel_scrollbar_drag); + return DID_NOT_HANDLE; + } +@@ -941,12 +956,14 @@ WebInputEventAttribution InputHandlerProxy::PerformEventAttribution( + // to check pointer lock state + gfx::PointF point = + static_cast(event).PositionInWidget(); ++ point = ScalePoint(point); + return WebInputEventAttribution( + WebInputEventAttribution::kTargetedFrame, + input_handler_->FindFrameElementIdAtPoint(point)); + } else if (WebInputEvent::IsGestureEventType(event.GetType())) { + gfx::PointF point = + static_cast(event).PositionInWidget(); ++ point = ScalePoint(point); + return WebInputEventAttribution( + WebInputEventAttribution::kTargetedFrame, + input_handler_->FindFrameElementIdAtPoint(point)); +@@ -961,6 +978,7 @@ WebInputEventAttribution InputHandlerProxy::PerformEventAttribution( + // how the renderer host performs touch event dispatch. + // https://cs.chromium.org/chromium/src/content/browser/renderer_host/render_widget_host_input_event_router.cc?l=808&rcl=10fe9d0a725d4ed7b69266a5936c525f0a5b26d3 + gfx::PointF point = touch_event.touches[0].PositionInWidget(); ++ point = ScalePoint(point); + const cc::ElementId targeted_element = + input_handler_->FindFrameElementIdAtPoint(point); + +@@ -1057,7 +1075,7 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleMouseWheel( + } + } + +- gfx::PointF position_in_widget = wheel_event.PositionInWidget(); ++ gfx::PointF position_in_widget = ScalePoint(wheel_event.PositionInWidget()); + if (input_handler_->HasBlockingWheelEventHandlerAt( + gfx::Point(position_in_widget.x(), position_in_widget.y()))) { + result = DID_NOT_HANDLE; +@@ -1226,7 +1244,7 @@ InputHandlerProxy::HandleGestureScrollUpdate( + scroll_data->set_unused_delta_y(scroll_result.unused_scroll_delta.y()); + }); + +- HandleOverscroll(gesture_event.PositionInWidget(), scroll_result); ++ HandleOverscroll(ScalePoint(gesture_event.PositionInWidget()), scroll_result); + + if (elastic_overscroll_controller_) + HandleScrollElasticityOverscroll(gesture_event, scroll_result); +@@ -1304,8 +1322,8 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HitTestTouchEvent( + cc::TouchAction touch_action = cc::TouchAction::kAuto; + cc::InputHandler::TouchStartOrMoveEventListenerType event_listener_type = + input_handler_->EventListenerTypeForTouchStartOrMoveAt( +- gfx::Point(touch_event.touches[i].PositionInWidget().x(), +- touch_event.touches[i].PositionInWidget().y()), ++ ScalePoint(gfx::Point(touch_event.touches[i].PositionInWidget().x(), ++ touch_event.touches[i].PositionInWidget().y())), + &touch_action); + if (allowed_touch_action && touch_action != cc::TouchAction::kAuto) { + TRACE_EVENT_INSTANT1("input", "Adding TouchAction", +@@ -1411,7 +1429,7 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchStart( + touch_event.touches_length == 1) { + DCHECK(touch_event.touches[0].state == WebTouchPoint::State::kStatePressed); + cc::InputHandlerPointerResult pointer_result = HandlePointerDown( +- event_with_callback, touch_event.touches[0].PositionInWidget()); ++ event_with_callback, ScalePoint(touch_event.touches[0].PositionInWidget())); + if (pointer_result.type == cc::PointerResultType::kScrollbarScroll) { + client_->SetAllowedTouchAction(allowed_touch_action); + return DID_HANDLE; +@@ -1474,7 +1492,7 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchMove( + touch_event.touch_start_or_first_touch_move); + if (touch_event.touches_length == 1) { + cc::InputHandlerPointerResult pointer_result = HandlePointerMove( +- event_with_callback, touch_event.touches[0].PositionInWidget(), ++ event_with_callback, ScalePoint(touch_event.touches[0].PositionInWidget()), + false /* should_cancel_scrollbar_drag */); + if (pointer_result.type == cc::PointerResultType::kScrollbarScroll) { + return DID_HANDLE; +@@ -1517,7 +1535,7 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchEnd( + touch_event.touches_length); + if (touch_event.touches_length == 1) { + cc::InputHandlerPointerResult pointer_result = HandlePointerUp( +- event_with_callback, touch_event.touches[0].PositionInWidget()); ++ event_with_callback, ScalePoint(touch_event.touches[0].PositionInWidget())); + if (pointer_result.type == cc::PointerResultType::kScrollbarScroll) { + return DID_HANDLE; + } +diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc +--- a/third_party/blink/renderer/platform/widget/widget_base.cc ++++ b/third_party/blink/renderer/platform/widget/widget_base.cc +@@ -1683,7 +1683,9 @@ void WidgetBase::RequestAnimationAfterDelayTimerFired(TimerBase*) { + client_->ScheduleAnimation(/*urgent=*/urgent_for_input); + } + +-float WidgetBase::GetOriginalDeviceScaleFactor() const { ++float WidgetBase::GetOriginalDeviceScaleFactor(bool real) const { ++ if (RuntimeEnabledFeatures::PixelPerfectEnabled() && real) ++ return 1.0f; + return client_->GetOriginalScreenInfos().current().device_scale_factor; + } + +@@ -1725,7 +1727,7 @@ void WidgetBase::UpdateSurfaceAndScreenInfo( + // Note carefully that the DSF specified in |new_screen_info| is not the + // DSF used by the compositor during device emulation! + LayerTreeHost()->SetViewportRectAndScale(compositor_viewport_pixel_rect, +- GetOriginalDeviceScaleFactor(), ++ GetOriginalDeviceScaleFactor(false), + local_surface_id_from_parent_); + // The VisualDeviceViewportIntersectionRect derives from the LayerTreeView's + // viewport size, which is set above. +@@ -1855,56 +1857,56 @@ void WidgetBase::CountDroppedPointerDownForEventTiming(unsigned count) { + gfx::PointF WidgetBase::DIPsToBlinkSpace(const gfx::PointF& point) { + // TODO(danakj): Should this use non-original scale factor so it changes under + // emulation? +- return gfx::ScalePoint(point, GetOriginalDeviceScaleFactor()); ++ return gfx::ScalePoint(point, GetOriginalDeviceScaleFactor(true)); + } + + gfx::Point WidgetBase::DIPsToRoundedBlinkSpace(const gfx::Point& point) { + // TODO(danakj): Should this use non-original scale factor so it changes under + // emulation? +- return gfx::ScaleToRoundedPoint(point, GetOriginalDeviceScaleFactor()); ++ return gfx::ScaleToRoundedPoint(point, GetOriginalDeviceScaleFactor(true)); + } + + gfx::PointF WidgetBase::BlinkSpaceToDIPs(const gfx::PointF& point) { + // TODO(danakj): Should this use non-original scale factor so it changes under + // emulation? +- return gfx::ScalePoint(point, 1.f / GetOriginalDeviceScaleFactor()); ++ return gfx::ScalePoint(point, 1.f / GetOriginalDeviceScaleFactor(true)); + } + + gfx::Point WidgetBase::BlinkSpaceToFlooredDIPs(const gfx::Point& point) { + // TODO(danakj): Should this use non-original scale factor so it changes under + // emulation? +- float reverse = 1 / GetOriginalDeviceScaleFactor(); ++ float reverse = 1 / GetOriginalDeviceScaleFactor(true); + return gfx::ScaleToFlooredPoint(point, reverse); + } + + gfx::Size WidgetBase::DIPsToCeiledBlinkSpace(const gfx::Size& size) { +- return gfx::ScaleToCeiledSize(size, GetOriginalDeviceScaleFactor()); ++ return gfx::ScaleToCeiledSize(size, GetOriginalDeviceScaleFactor(false)); + } + + gfx::RectF WidgetBase::DIPsToBlinkSpace(const gfx::RectF& rect) { + // TODO(danakj): Should this use non-original scale factor so it changes under + // emulation? +- return gfx::ScaleRect(rect, GetOriginalDeviceScaleFactor()); ++ return gfx::ScaleRect(rect, GetOriginalDeviceScaleFactor(true)); + } + + float WidgetBase::DIPsToBlinkSpace(float scalar) { + // TODO(danakj): Should this use non-original scale factor so it changes under + // emulation? +- return GetOriginalDeviceScaleFactor() * scalar; ++ return GetOriginalDeviceScaleFactor(true) * scalar; + } + + gfx::Size WidgetBase::BlinkSpaceToFlooredDIPs(const gfx::Size& size) { +- float reverse = 1 / GetOriginalDeviceScaleFactor(); ++ float reverse = 1 / GetOriginalDeviceScaleFactor(false); + return gfx::ScaleToFlooredSize(size, reverse); + } + + gfx::Rect WidgetBase::BlinkSpaceToEnclosedDIPs(const gfx::Rect& rect) { +- float reverse = 1 / GetOriginalDeviceScaleFactor(); ++ float reverse = 1 / GetOriginalDeviceScaleFactor(false); + return gfx::ScaleToEnclosedRect(rect, reverse); + } + + gfx::RectF WidgetBase::BlinkSpaceToDIPs(const gfx::RectF& rect) { +- float reverse = 1 / GetOriginalDeviceScaleFactor(); ++ float reverse = 1 / GetOriginalDeviceScaleFactor(false); + return gfx::ScaleRect(rect, reverse); + } + +diff --git a/third_party/blink/renderer/platform/widget/widget_base.h b/third_party/blink/renderer/platform/widget/widget_base.h +--- a/third_party/blink/renderer/platform/widget/widget_base.h ++++ b/third_party/blink/renderer/platform/widget/widget_base.h +@@ -404,7 +404,7 @@ class PLATFORM_EXPORT WidgetBase : public mojom::blink::Widget, + void OnDevToolsSessionConnectionChanged(bool attached); + + // Helper to get the non-emulated device scale factor. +- float GetOriginalDeviceScaleFactor() const; ++ float GetOriginalDeviceScaleFactor(bool real) const; + + // Requests that the callback be invoked after the next frame is generated and + // presented in the display compositor. Returns true if the callback was +diff --git a/ui/android/delegated_frame_host_android.cc b/ui/android/delegated_frame_host_android.cc +--- a/ui/android/delegated_frame_host_android.cc ++++ b/ui/android/delegated_frame_host_android.cc +@@ -180,7 +180,7 @@ const viz::FrameSinkId& DelegatedFrameHostAndroid::GetFrameSinkId() const { + return frame_sink_id_; + } + +-void DelegatedFrameHostAndroid::CopyFromCompositingSurface( ++void DelegatedFrameHostAndroid::CopyFromCompositingSurface( // POI + const gfx::Rect& src_subrect, + const gfx::Size& output_size, + base::OnceCallback callback, +diff --git a/ui/android/event_forwarder.cc b/ui/android/event_forwarder.cc +--- a/ui/android/event_forwarder.cc ++++ b/ui/android/event_forwarder.cc +@@ -138,7 +138,7 @@ jboolean EventForwarder::OnTouchEvent(JNIEnv* env, + // milliseconds timestamp. + base::TimeTicks down_time = base::TimeTicks::FromUptimeMillis(down_time_ms); + ui::MotionEventAndroidJava event( +- env, motion_event.obj(), 1.f / view_->GetDipScale(), 0.f, 0.f, 0.f, ++ env, motion_event.obj(), 1.f / view_->GetDipScaleFactor(), 0.f, 0.f, 0.f, + base::TimeTicks::FromJavaNanoTime(oldest_event_time_ns), + base::TimeTicks::FromJavaNanoTime(latest_event_time_ns), down_time, + android_action, pointer_count, history_size, action_index, +@@ -185,7 +185,7 @@ void EventForwarder::OnMouseEvent(JNIEnv* env, + /*pressure=*/pressure, /*orientation_rad=*/orientation, /*tilt_rad=*/tilt, + /*tool_type=*/android_tool_type); + ui::MotionEventAndroidJava event( +- env, nullptr /* event */, 1.f / view_->GetDipScale(), 0.f, 0.f, 0.f, ++ env, nullptr /* event */, 1.f / view_->GetDipScaleFactor(), 0.f, 0.f, 0.f, + base::TimeTicks::FromJavaNanoTime(time_ns), android_action, + 1 /* pointer_count */, 0 /* history_size */, 0 /* action_index */, + android_action_button, 0 /* gesture_classification */, +@@ -211,7 +211,7 @@ void EventForwarder::OnDragEvent(JNIEnv* env, + const JavaParamRef& j_text, + const JavaParamRef& j_html, + const JavaParamRef& j_url) { +- float dip_scale = view_->GetDipScale(); ++ float dip_scale = view_->GetDipScaleFactor(); + gfx::PointF location(x / dip_scale, y / dip_scale); + gfx::PointF root_location(screen_x / dip_scale, screen_y / dip_scale); + std::vector mime_types; +@@ -228,7 +228,7 @@ jboolean EventForwarder::OnGestureEvent(JNIEnv* env, + jint type, + jlong time_ms, + jfloat scale) { +- float dip_scale = view_->GetDipScale(); ++ float dip_scale = view_->GetDipScaleFactor(); + auto size = view_->GetSizeDIPs(); + float x = size.width() / 2; + float y = size.height() / 2; +@@ -258,7 +258,7 @@ jboolean EventForwarder::OnGenericMotionEvent( + // milliseconds timestamp. + base::TimeTicks down_time = base::TimeTicks::FromUptimeMillis(down_time_ms); + ui::MotionEventAndroidJava event( +- env, motion_event.obj(), 1.f / view_->GetDipScale(), 0.f, 0.f, 0.f, ++ env, motion_event.obj(), 1.f / view_->GetDipScaleFactor(), 0.f, 0.f, 0.f, + base::TimeTicks::FromJavaNanoTime(event_time_ns), + base::TimeTicks::FromJavaNanoTime(event_time_ns), down_time, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, false, &pointer0, nullptr, false); +@@ -301,7 +301,7 @@ void EventForwarder::DoubleTap(JNIEnv* env, + jlong time_ms, + jint x, + jint y) { +- float dip_scale = view_->GetDipScale(); ++ float dip_scale = view_->GetDipScaleFactor(); + view_->OnGestureEvent(GestureEventAndroid( + GESTURE_EVENT_TYPE_DOUBLE_TAP, gfx::PointF(x / dip_scale, y / dip_scale), + gfx::PointF(), time_ms, 0, 0, 0, 0, 0, /*target_viewport*/ true, +@@ -319,7 +319,7 @@ void EventForwarder::StartFling(JNIEnv* env, + + if (velocity_x == 0 && velocity_y == 0) + return; +- float dip_scale = view_->GetDipScale(); ++ float dip_scale = view_->GetDipScaleFactor(); + // Use velocity as delta in scroll event. + view_->OnGestureEvent(GestureEventAndroid( + GESTURE_EVENT_TYPE_SCROLL_START, gfx::PointF(), gfx::PointF(), time_ms, 0, +diff --git a/ui/android/java/src/org/chromium/ui/base/UiAndroidFeatureList.java b/ui/android/java/src/org/chromium/ui/base/UiAndroidFeatureList.java +--- a/ui/android/java/src/org/chromium/ui/base/UiAndroidFeatureList.java ++++ b/ui/android/java/src/org/chromium/ui/base/UiAndroidFeatureList.java +@@ -41,6 +41,12 @@ public class UiAndroidFeatureList { + /* defaultValue= */ false, + /* defaultValueInTests= */ true); + ++ public static final CachedFlag sPixelPerfect = ++ newCachedFlag( ++ "AndroidPixelPerfect", ++ /* defaultValue= */ false, ++ /* defaultValueInTests= */ false); ++ + public static final List sFlagsCachedUiAndroid = + List.of(sAndroidWindowOcclusion, sAndroidWindowManagementWebApi); + } +diff --git a/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java b/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java +--- a/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java ++++ b/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java +@@ -33,6 +33,7 @@ import org.chromium.base.ThreadUtils; + import org.chromium.build.annotations.EnsuresNonNull; + import org.chromium.build.annotations.NullMarked; + import org.chromium.build.annotations.Nullable; ++import org.chromium.ui.base.UiAndroidFeatureList; + import org.chromium.ui.util.XrUtils; + + import java.util.Arrays; +@@ -330,6 +331,15 @@ import java.util.function.Consumer; + float ydpi, + Display display) { + if (hasForcedDIPScale()) density = sForcedDIPScale.floatValue(); ++ if (UiAndroidFeatureList.sPixelPerfect.isEnabled()) { ++ if (bounds.right < bounds.bottom) { ++ density = (float)(Math.round(bounds.right / 412.0 * 1000.0) / 1000.0); ++ bounds = new Rect(bounds.left, bounds.top, 412, bounds.bottom); ++ } else { ++ density = (float)(Math.round(bounds.bottom / 412.0 * 1000.0) / 1000.0); ++ bounds = new Rect(bounds.left, bounds.top, bounds.right, 412); ++ } ++ } + boolean isWideColorGamut = false; + // Although this API was added in Android O, it was buggy. + // Restrict to Android Q, where it was fixed. +diff --git a/ui/android/view_android.cc b/ui/android/view_android.cc +--- a/ui/android/view_android.cc ++++ b/ui/android/view_android.cc +@@ -117,6 +117,10 @@ float ViewAndroid::GetDipScale() { + return ui::GetScaleFactorForNativeView(this); + } + ++float ViewAndroid::GetDipScaleFactor() { ++ return ui::GetScaleFactorForNativeView(this); ++} ++ + ScopedJavaLocalRef ViewAndroid::GetEventForwarder() { + if (!event_forwarder_) { + DCHECK(!RootPathHasEventForwarder(parent_)) +@@ -152,7 +156,7 @@ void ViewAndroid::AddChild(ViewAndroid* child) { + // spurious events with empty size from being sent down. + if (child->match_parent() && !bounds_device_px_.IsEmpty() && + child->GetSizeDevicePx() != bounds_device_px_.size()) { +- child->OnSizeChangedInternal(bounds_device_px_.size()); ++ child->OnSizeChangedInternal(bounds_device_px_.size(), bounds_real_.size()); + child->DispatchOnSizeChanged(); + } + +@@ -488,7 +492,7 @@ void ViewAndroid::OnVerticalScrollDirectionChanged(bool direction_up, + env, delegate, direction_up, current_scroll_ratio); + } + +-void ViewAndroid::OnSizeChanged(int width, int height) { ++void ViewAndroid::OnSizeChanged(int width, int height, int real_width, int real_height) { + // Match-parent view must not receive size events. + DCHECK(!match_parent()); + +@@ -498,13 +502,15 @@ void ViewAndroid::OnSizeChanged(int width, int height) { + return; + } + +- OnSizeChangedInternal(size_device_px); ++ gfx::Size bounds(real_width, real_height); ++ OnSizeChangedInternal(size_device_px, bounds); + + // Signal resize event after all the views in the tree get the updated size. + DispatchOnSizeChanged(); + } + +-void ViewAndroid::OnSizeChangedInternal(const gfx::Size& size_device_px) { ++void ViewAndroid::OnSizeChangedInternal(const gfx::Size& size_device_px, const gfx::Size& size_real) { ++ bounds_real_.set_size(size_real); + if (bounds_device_px_.size() == size_device_px) { + return; + } +@@ -512,12 +518,15 @@ void ViewAndroid::OnSizeChangedInternal(const gfx::Size& size_device_px) { + bounds_device_px_.set_size(size_device_px); + + float scale = GetDipScale(); +- bounds_dips_.set_size(gfx::Size(std::ceil(size_device_px.width() / scale), +- std::ceil(size_device_px.height() / scale))); ++ int w = std::ceil(size_device_px.width() / scale); ++ int h = std::ceil(size_device_px.height() / scale); ++ if (w >= 411 && w <= 413) w = 412; ++ if (h >= 411 && h <= 413) h = 412; ++ bounds_dips_.set_size(gfx::Size(w, h)); + + for (ViewAndroid* child : children_) { + if (child->match_parent()) +- child->OnSizeChangedInternal(size_device_px); ++ child->OnSizeChangedInternal(size_device_px, size_real); + } + } + +@@ -568,6 +577,10 @@ gfx::Size ViewAndroid::GetSizeDevicePx() const { + return bounds_device_px_.size(); + } + ++gfx::Size ViewAndroid::GetSizeReal() const { ++ return bounds_real_.size(); ++} ++ + bool ViewAndroid::OnDragEvent(const DragEventAndroid& event) { + return HitTest(base::BindRepeating(&ViewAndroid::SendDragEventToHandler), + event, event.location()); +diff --git a/ui/android/view_android.h b/ui/android/view_android.h +--- a/ui/android/view_android.h ++++ b/ui/android/view_android.h +@@ -125,6 +125,8 @@ class UI_ANDROID_EXPORT ViewAndroid { + // Virtual for testing. + virtual float GetDipScale(); + ++ float GetDipScaleFactor(); ++ + cc::slim::Layer* GetLayer() const; + void SetLayer(scoped_refptr layer); + +@@ -169,10 +171,11 @@ class UI_ANDROID_EXPORT ViewAndroid { + + gfx::Size GetPhysicalBackingSize() const; + gfx::Size GetSizeDIPs() const; ++ gfx::Size GetSizeReal() const; + gfx::Size GetSizeDevicePx() const; + + // |width| and |height| are in device pixels. +- void OnSizeChanged(int width, int height); ++ void OnSizeChanged(int width, int height, int real_width, int real_height); + // |deadline_override| if not nullopt will be used as the cc::DeadlinePolicy + // timeout for this resize. + void OnPhysicalBackingSizeChanged( +@@ -311,7 +314,7 @@ class UI_ANDROID_EXPORT ViewAndroid { + // each leaf of subtree. + static bool SubtreeHasEventForwarder(ViewAndroid* view); + +- void OnSizeChangedInternal(const gfx::Size& size_device_px); ++ void OnSizeChangedInternal(const gfx::Size& size_device_px, const gfx::Size& size_real); + void DispatchOnSizeChanged(); + + // Returns the Java delegate for this view. This is used to delegate work +@@ -334,6 +337,8 @@ class UI_ANDROID_EXPORT ViewAndroid { + // Same as above, but before dividing by the device scale factor. + gfx::Rect bounds_device_px_; + ++ gfx::Rect bounds_real_; ++ + const LayoutType layout_type_; + + // In physical pixel. +diff --git a/ui/display/mojom/screen_info.mojom b/ui/display/mojom/screen_info.mojom +--- a/ui/display/mojom/screen_info.mojom ++++ b/ui/display/mojom/screen_info.mojom +@@ -17,6 +17,7 @@ struct ScreenInfo { + // Device scale factor. Specifies the ratio between physical and logical + // pixels. + float device_scale_factor = 1; ++ float device_scale_factor_original = 0; + + // The color spaces and buffer formats that this screen will use for various + // content types. +diff --git a/ui/display/mojom/screen_info_mojom_traits.cc b/ui/display/mojom/screen_info_mojom_traits.cc +--- a/ui/display/mojom/screen_info_mojom_traits.cc ++++ b/ui/display/mojom/screen_info_mojom_traits.cc +@@ -19,6 +19,7 @@ bool StructTraits:: + } + + out->device_scale_factor = data.device_scale_factor(); ++ out->device_scale_factor_original = data.device_scale_factor_original(); + out->depth = data.depth(); + out->depth_per_component = data.depth_per_component(); + out->is_monochrome = data.is_monochrome(); +diff --git a/ui/display/mojom/screen_info_mojom_traits.h b/ui/display/mojom/screen_info_mojom_traits.h +--- a/ui/display/mojom/screen_info_mojom_traits.h ++++ b/ui/display/mojom/screen_info_mojom_traits.h +@@ -19,6 +19,10 @@ struct COMPONENT_EXPORT(DISPLAY_SHARED_MOJOM_TRAITS) + return r.device_scale_factor; + } + ++ static float device_scale_factor_original(const display::ScreenInfo& r) { ++ return r.device_scale_factor_original; ++ } ++ + static const gfx::DisplayColorSpaces& display_color_spaces( + const display::ScreenInfo& r) { + return r.display_color_spaces; +diff --git a/ui/display/screen_info.h b/ui/display/screen_info.h +--- a/ui/display/screen_info.h ++++ b/ui/display/screen_info.h +@@ -22,6 +22,7 @@ struct DISPLAY_EXPORT ScreenInfo { + // Device scale factor. Specifies the ratio between physical and logical + // pixels. + float device_scale_factor = 1.f; ++ float device_scale_factor_original = 0; + + // The color spaces used by output display for various content types. + gfx::DisplayColorSpaces display_color_spaces; +diff --git a/ui/events/blink/blink_event_util.cc b/ui/events/blink/blink_event_util.cc +--- a/ui/events/blink/blink_event_util.cc ++++ b/ui/events/blink/blink_event_util.cc +@@ -161,7 +161,7 @@ WebPointerProperties::Button ToWebPointerButton(int android_button_state) { + return WebPointerProperties::Button::kNoButton; + } + +-WebTouchPoint CreateWebTouchPoint(const MotionEvent& event, ++WebTouchPoint CreateWebTouchPoint(const MotionEvent& event, // POI + size_t pointer_index) { + WebTouchPoint touch; + +--