Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(679)

Unified Diff: content/browser/iframe_zoom_browsertest.cc

Issue 1804023002: Fix page zoom to be frame-centric for out-of-process frames. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address comments. Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/browser/iframe_zoom_browsertest.cc
diff --git a/content/browser/iframe_zoom_browsertest.cc b/content/browser/iframe_zoom_browsertest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..02b6f532df808cad8e9089327b0da412489b7346
--- /dev/null
+++ b/content/browser/iframe_zoom_browsertest.cc
@@ -0,0 +1,430 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "content/browser/frame_host/frame_tree_node.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/host_zoom_map.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/common/page_zoom.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/shell/browser/shell.h"
+#include "content/test/content_browser_test_utils_internal.h"
+#include "net/dns/mock_host_resolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// This file contains tests to make sure that subframes zoom in a manner
+// consistent with the top-level frame, even when the subframes are cross-site.
+// Particular things we want to make sure of:
+//
+// * Subframes should always have the same zoom level as their main frame, even
+// if the subframe's domain has a different zoom level stored in HostZoomMap.
+//
+// * The condition above should continue to hold after a navigation of the
+// subframe.
+//
+// * Zoom changes applied to the mainframe should propagate to all subframes,
+// regardless of whether they are same site or cross-site to the frame they are
+// children of.
+//
+// The tests in this file rely on the notion that, when a page zooms, that
+// subframes have both (1) a change in their frame rect, and (2) a change in
+// their frame's scale. Since the page should scale as a unit, this means the
+// innerWidth value of any subframe should be the same before and after the
+// zoom (though it may transiently take on a different value). The
+// FrameSizeObserver serves to watch for onresize events, and observes when
+// the innerWidth is correctly set.
+class IFrameZoomBrowserTest : public ContentBrowserTest {
+ public:
+ IFrameZoomBrowserTest() {}
+
+ protected:
+ void SetUpOnMainThread() override {
+ host_resolver()->AddRule("*", "127.0.0.1");
+ ASSERT_TRUE(embedded_test_server()->Start());
+ SetupCrossSiteRedirector(embedded_test_server());
+ }
+
+ WebContentsImpl* web_contents() {
+ return static_cast<WebContentsImpl*>(shell()->web_contents());
+ }
+};
+
+namespace {
+
+const double kTolerance = 0.1; // In CSS pixels.
+
+double GetMainframeWindowBorder(const ToRenderFrameHost& adapter) {
+ double border;
+ const char kGetMainframeBorder[] = "window.domAutomationController.send("
+ "window.outerWidth - window.innerWidth"
+ ");";
+ EXPECT_TRUE(
+ ExecuteScriptAndExtractDouble(adapter, kGetMainframeBorder, &border));
+ return border;
+}
+
+double GetMainFrameZoomFactor(const ToRenderFrameHost& adapter, double border) {
+ const char kGetMainFrameZoomLevel[] =
+ "window.domAutomationController.send("
+ "(window.outerWidth - %f)/window.innerWidth"
+ ");";
+ double zoom_factor;
+ EXPECT_TRUE(ExecuteScriptAndExtractDouble(
+ adapter, base::StringPrintf(kGetMainFrameZoomLevel, border),
+ &zoom_factor));
+ return zoom_factor;
+}
+
+double GetSubframeWidth(const ToRenderFrameHost& adapter) {
+ double width;
+ EXPECT_TRUE(ExecuteScriptAndExtractDouble(
+ adapter, "window.domAutomationController.send(window.innerWidth);",
+ &width));
+ return width;
+}
+
+// This struct is used to track changes to subframes after a main frame zoom
+// change, so that we can test subframe inner widths with assurance that all the
+// changes have finished propagating.
+struct FrameResizeObserver {
+ FrameResizeObserver(RenderFrameHost* host,
+ std::string label,
+ double inner_width,
+ double tolerance)
+ : frame_host(host),
+ msg_label(std::move(label)),
+ zoomed_correctly(false),
+ expected_inner_width(inner_width),
+ tolerance(tolerance) {
+ SetupOnResizeCallback(host, msg_label);
+ }
+
+ void SetupOnResizeCallback(const ToRenderFrameHost& adapter,
+ const std::string& label) {
+ const char kOnResizeCallbackSetup[] =
+ "document.body.onresize = function(){"
+ " window.domAutomationController.setAutomationId(0);"
+ " window.domAutomationController.send('%s ' + window.innerWidth);"
+ "};";
+ EXPECT_TRUE(ExecuteScript(
+ adapter, base::StringPrintf(kOnResizeCallbackSetup, label.c_str())));
+ }
+
+ void Check(const std::string& status_msg) {
+ if (status_msg.find(msg_label) != 0)
+ return;
+
+ double inner_width = std::stod(status_msg.substr(msg_label.length() + 1));
+ zoomed_correctly = std::abs(expected_inner_width - inner_width) < tolerance;
+ }
+
+ FrameResizeObserver* toThis() {return this;}
+
+ RenderFrameHost* frame_host;
+ std::string msg_label;
+ bool zoomed_correctly;
+ double expected_inner_width;
+ double tolerance;
+};
+
+void WaitAndCheckFrameZoom(
+ DOMMessageQueue& msg_queue,
+ std::vector<FrameResizeObserver>& frame_observers) {
+ std::string status;
+ while (msg_queue.WaitForMessage(&status)) {
+ // Strip the double quotes from the message.
+ status = status.substr(1, status.length() -2);
+
+ bool all_zoomed_correctly = true;
+
+ // Use auto& to operate on a reference, and not a copy.
+ for (auto& observer : frame_observers) {
+ observer.Check(status);
+ all_zoomed_correctly = all_zoomed_correctly && observer.zoomed_correctly;
+ }
+
+ if (all_zoomed_correctly)
+ break;
+ }
+}
+
+} // namespace anonymous
ncarter (slow) 2016/04/28 22:30:14 } // namespace (no 'anonymous') https://google.g
wjmaclean 2016/04/29 13:40:41 Actually, is that right? The link shows a formatti
ncarter (slow) 2016/04/29 19:01:56 Because over the over-arching rule about "be consi
+
+IN_PROC_BROWSER_TEST_F(IFrameZoomBrowserTest, SubframesZoomProperly) {
+ std::string top_level_host("a.com");
+ GURL main_url(embedded_test_server()->GetURL(
+ top_level_host, "/cross_site_iframe_factory.html?a(b(a))"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+ NavigationEntry* entry =
+ web_contents()->GetController().GetLastCommittedEntry();
+ ASSERT_TRUE(entry);
+ GURL loaded_url = HostZoomMap::GetURLFromEntry(entry);
+ EXPECT_EQ(top_level_host, loaded_url.host());
+
+ FrameTreeNode* root =
+ static_cast<WebContentsImpl*>(web_contents())->GetFrameTree()->root();
+ RenderFrameHostImpl* child = root->child_at(0)->current_frame_host();
+ RenderFrameHostImpl* grandchild =
+ root->child_at(0)->child_at(0)->current_frame_host();
+
+ // The following calls must be made when the page's scale factor = 1.0.
+ double scale_one_child_width = GetSubframeWidth(child);
+ double scale_one_grandchild_width = GetSubframeWidth(grandchild);
+ double main_frame_window_border = GetMainframeWindowBorder(web_contents());
+
+ HostZoomMap* host_zoom_map = HostZoomMap::GetForWebContents(web_contents());
+ double default_zoom_level = host_zoom_map->GetDefaultZoomLevel();
+ EXPECT_EQ(0.0, default_zoom_level);
+
+ EXPECT_DOUBLE_EQ(
+ 1.0, GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+
+ const double new_zoom_factor = 2.5;
+ {
+ DOMMessageQueue msg_queue;
+
+ std::vector<FrameResizeObserver> frame_observers;
+ frame_observers.emplace_back(child, "child",
+ scale_one_child_width, kTolerance);
+ frame_observers.emplace_back(grandchild, "grandchild",
+ scale_one_grandchild_width, kTolerance);
+
+ const double new_zoom_level =
+ default_zoom_level + ZoomFactorToZoomLevel(new_zoom_factor);
+ host_zoom_map->SetZoomLevelForHost(top_level_host, new_zoom_level);
+
+ WaitAndCheckFrameZoom(msg_queue, frame_observers);
+ }
+
+ EXPECT_DOUBLE_EQ(
+ new_zoom_factor,
+ GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+}
+
+IN_PROC_BROWSER_TEST_F(IFrameZoomBrowserTest, SubframesDontZoomIndependently) {
+ std::string top_level_host("a.com");
+ GURL main_url(embedded_test_server()->GetURL(
+ top_level_host, "/cross_site_iframe_factory.html?a(b(a))"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+ NavigationEntry* entry =
+ web_contents()->GetController().GetLastCommittedEntry();
+ ASSERT_TRUE(entry);
+ GURL loaded_url = HostZoomMap::GetURLFromEntry(entry);
+ EXPECT_EQ(top_level_host, loaded_url.host());
+
+ FrameTreeNode* root =
+ static_cast<WebContentsImpl*>(web_contents())->GetFrameTree()->root();
+ RenderFrameHostImpl* child = root->child_at(0)->current_frame_host();
+ RenderFrameHostImpl* grandchild =
+ root->child_at(0)->child_at(0)->current_frame_host();
+
+ // The following calls must be made when the page's scale factor = 1.0.
+ double scale_one_child_width = GetSubframeWidth(child);
+ double scale_one_grandchild_width = GetSubframeWidth(grandchild);
+ double main_frame_window_border = GetMainframeWindowBorder(web_contents());
+
+ HostZoomMap* host_zoom_map = HostZoomMap::GetForWebContents(web_contents());
+ double default_zoom_level = host_zoom_map->GetDefaultZoomLevel();
+ EXPECT_EQ(0.0, default_zoom_level);
+
+ EXPECT_DOUBLE_EQ(
+ 1.0, GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+
+ const double new_zoom_factor = 2.0;
+ const double new_zoom_level =
+ default_zoom_level + ZoomFactorToZoomLevel(new_zoom_factor);
+
+ // This should not cause the nested iframe to change its zoom.
+ host_zoom_map->SetZoomLevelForHost("b.com", new_zoom_level);
+
+ EXPECT_DOUBLE_EQ(
+ 1.0, GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+ EXPECT_EQ(scale_one_child_width, GetSubframeWidth(child));
+ EXPECT_EQ(scale_one_grandchild_width, GetSubframeWidth(grandchild));
+
+ // We exclude the remainder of this test on Android since Android does not
+ // set page zoom levels for loading pages.
+ // See RenderViewImpl::OnSetZoomLevelForLoadingURL().
+#if !defined(OS_ANDROID)
+ // When we navigate so that b.com is the top-level site, then it has the
+ // expected zoom.
+ GURL new_url = embedded_test_server()->GetURL("b.com", "/title1.html");
+ EXPECT_TRUE(NavigateToURL(shell(), new_url));
+ EXPECT_DOUBLE_EQ(
+ new_zoom_factor,
+ GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+#endif
+}
+
+IN_PROC_BROWSER_TEST_F(IFrameZoomBrowserTest, AllFramesGetDefaultZoom) {
+ std::string top_level_host("a.com");
+ GURL main_url(embedded_test_server()->GetURL(
+ top_level_host, "/cross_site_iframe_factory.html?a(b(a))"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+ NavigationEntry* entry =
+ web_contents()->GetController().GetLastCommittedEntry();
+ ASSERT_TRUE(entry);
+ GURL loaded_url = HostZoomMap::GetURLFromEntry(entry);
+ EXPECT_EQ(top_level_host, loaded_url.host());
+
+ FrameTreeNode* root =
+ static_cast<WebContentsImpl*>(web_contents())->GetFrameTree()->root();
+ RenderFrameHostImpl* child = root->child_at(0)->current_frame_host();
+ RenderFrameHostImpl* grandchild =
+ root->child_at(0)->child_at(0)->current_frame_host();
+
+ // The following calls must be made when the page's scale factor = 1.0.
+ double scale_one_child_width = GetSubframeWidth(child);
+ double scale_one_grandchild_width = GetSubframeWidth(grandchild);
+ double main_frame_window_border = GetMainframeWindowBorder(web_contents());
+
+ HostZoomMap* host_zoom_map = HostZoomMap::GetForWebContents(web_contents());
+ double default_zoom_level = host_zoom_map->GetDefaultZoomLevel();
+ EXPECT_EQ(0.0, default_zoom_level);
+
+ EXPECT_DOUBLE_EQ(
+ 1.0, GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+
+ const double new_default_zoom_factor = 2.0;
+ {
+ DOMMessageQueue msg_queue;
+
+ std::vector<FrameResizeObserver> frame_observers;
+ frame_observers.emplace_back(child, "child",
+ scale_one_child_width, kTolerance);
+ frame_observers.emplace_back(grandchild, "grandchild",
+ scale_one_grandchild_width, kTolerance);
+
+ const double new_default_zoom_level =
+ default_zoom_level + ZoomFactorToZoomLevel(new_default_zoom_factor);
+
+ host_zoom_map->SetZoomLevelForHost("b.com", new_default_zoom_level + 1.0);
+ host_zoom_map->SetDefaultZoomLevel(new_default_zoom_level);
+
+ WaitAndCheckFrameZoom(msg_queue, frame_observers);
+ }
+ EXPECT_DOUBLE_EQ(
+ new_default_zoom_factor,
+ GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+}
+
+IN_PROC_BROWSER_TEST_F(IFrameZoomBrowserTest, SiblingFramesZoom) {
+ std::string top_level_host("a.com");
+ GURL main_url(embedded_test_server()->GetURL(
+ top_level_host, "/cross_site_iframe_factory.html?a(b,b)"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+ NavigationEntry* entry =
+ web_contents()->GetController().GetLastCommittedEntry();
+ ASSERT_TRUE(entry);
+ GURL loaded_url = HostZoomMap::GetURLFromEntry(entry);
+ EXPECT_EQ(top_level_host, loaded_url.host());
+
+ FrameTreeNode* root =
+ static_cast<WebContentsImpl*>(web_contents())->GetFrameTree()->root();
+ RenderFrameHostImpl* child1 = root->child_at(0)->current_frame_host();
+ RenderFrameHostImpl* child2 = root->child_at(1)->current_frame_host();
+
+ // The following calls must be made when the page's scale factor = 1.0.
+ double scale_one_child1_width = GetSubframeWidth(child1);
+ double scale_one_child2_width = GetSubframeWidth(child2);
+ double main_frame_window_border = GetMainframeWindowBorder(web_contents());
+
+ HostZoomMap* host_zoom_map = HostZoomMap::GetForWebContents(web_contents());
+ double default_zoom_level = host_zoom_map->GetDefaultZoomLevel();
+ EXPECT_EQ(0.0, default_zoom_level);
+
+ EXPECT_DOUBLE_EQ(
+ 1.0, GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+
+ const double new_zoom_factor = 2.5;
+ {
+ DOMMessageQueue msg_queue;
+
+ std::vector<FrameResizeObserver> frame_observers;
+ frame_observers.emplace_back(child1, "child1",
+ scale_one_child1_width, kTolerance);
+ frame_observers.emplace_back(child2, "child2",
+ scale_one_child2_width, kTolerance);
+
+ const double new_zoom_level =
+ default_zoom_level + ZoomFactorToZoomLevel(new_zoom_factor);
+ host_zoom_map->SetZoomLevelForHost(top_level_host, new_zoom_level);
+
+ WaitAndCheckFrameZoom(msg_queue, frame_observers);
+ }
+
+ EXPECT_DOUBLE_EQ(
+ new_zoom_factor,
+ GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+}
+
+IN_PROC_BROWSER_TEST_F(IFrameZoomBrowserTest, SubframeRetainsZoomOnNavigation) {
+ std::string top_level_host("a.com");
+ GURL main_url(embedded_test_server()->GetURL(
+ top_level_host, "/cross_site_iframe_factory.html?a(b)"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+ NavigationEntry* entry =
+ web_contents()->GetController().GetLastCommittedEntry();
+ ASSERT_TRUE(entry);
+ GURL loaded_url = HostZoomMap::GetURLFromEntry(entry);
+ EXPECT_EQ(top_level_host, loaded_url.host());
+
+ FrameTreeNode* root =
+ static_cast<WebContentsImpl*>(web_contents())->GetFrameTree()->root();
+ RenderFrameHostImpl* child = root->child_at(0)->current_frame_host();
+
+ // The following calls must be made when the page's scale factor = 1.0.
+ double scale_one_child_width = GetSubframeWidth(child);
+ double main_frame_window_border = GetMainframeWindowBorder(web_contents());
+
+ HostZoomMap* host_zoom_map = HostZoomMap::GetForWebContents(web_contents());
+ double default_zoom_level = host_zoom_map->GetDefaultZoomLevel();
+ EXPECT_EQ(0.0, default_zoom_level);
+
+ EXPECT_DOUBLE_EQ(
+ 1.0, GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+
+ const double new_zoom_factor = 0.5;
+ {
+ DOMMessageQueue msg_queue;
+
+ std::vector<FrameResizeObserver> frame_observers;
+ frame_observers.emplace_back(child, "child",
+ scale_one_child_width, kTolerance);
+
+ const double new_zoom_level =
+ default_zoom_level + ZoomFactorToZoomLevel(new_zoom_factor);
+ host_zoom_map->SetZoomLevelForHost(top_level_host, new_zoom_level);
+
+ WaitAndCheckFrameZoom(msg_queue, frame_observers);
+ }
+
+ EXPECT_DOUBLE_EQ(
+ new_zoom_factor,
+ GetMainFrameZoomFactor(web_contents(), main_frame_window_border));
+
+ // Navigate child frame cross site, and make sure zoom is the same.
+ TestNavigationObserver observer(web_contents());
+ GURL url = embedded_test_server()->GetURL("c.com", "/title1.html");
+ NavigateFrameToURL(root->child_at(0), url);
+ EXPECT_TRUE(observer.last_navigation_succeeded());
+ EXPECT_EQ(url, observer.last_navigation_url());
+
+ // Check that the child frame maintained the same scale after navigating
+ // cross-site.
+ double new_child_width =
+ GetSubframeWidth(root->child_at(0)->current_frame_host());
+ EXPECT_EQ(scale_one_child_width, new_child_width);
+}
+
+} // namespace content

Powered by Google App Engine
This is Rietveld 408576698