| // Copyright (c) 2013 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 <stdint.h> |
| |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "content/browser/gpu/compositor_util.h" |
| #include "content/browser/renderer_host/dip_util.h" |
| #include "content/browser/renderer_host/render_widget_host_impl.h" |
| #include "content/browser/renderer_host/render_widget_host_view_base.h" |
| #include "content/common/frame_messages.h" |
| #include "content/public/browser/gpu_data_manager.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_paths.h" |
| #include "content/public/common/url_constants.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_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/did_commit_provisional_load_interceptor.h" |
| #include "net/base/filename_util.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/base/layout.h" |
| #include "ui/display/display_switches.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Convenience macro: Short-circuit a pass for the tests where platform support |
| // for forced-compositing mode (or disabled-compositing mode) is lacking. |
| #define SET_UP_SURFACE_OR_PASS_TEST(wait_message) \ |
| if (!SetUpSourceSurface(wait_message)) { \ |
| LOG(WARNING) \ |
| << ("Blindly passing this test: This platform does not support " \ |
| "forced compositing (or forced-disabled compositing) mode."); \ |
| return; \ |
| } |
| |
| // Common base class for browser tests. This is subclassed twice: Once to test |
| // the browser in forced-compositing mode, and once to test with compositing |
| // mode disabled. |
| class RenderWidgetHostViewBrowserTest : public ContentBrowserTest { |
| public: |
| RenderWidgetHostViewBrowserTest() |
| : frame_size_(400, 300), |
| callback_invoke_count_(0), |
| frames_captured_(0) {} |
| |
| void SetUpOnMainThread() override { |
| ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_dir_)); |
| } |
| |
| // Attempts to set up the source surface. Returns false if unsupported on the |
| // current platform. |
| virtual bool SetUpSourceSurface(const char* wait_message) = 0; |
| |
| int callback_invoke_count() const { |
| return callback_invoke_count_; |
| } |
| |
| int frames_captured() const { |
| return frames_captured_; |
| } |
| |
| const gfx::Size& frame_size() const { |
| return frame_size_; |
| } |
| |
| const base::FilePath& test_dir() const { |
| return test_dir_; |
| } |
| |
| RenderViewHost* GetRenderViewHost() const { |
| RenderViewHost* const rvh = shell()->web_contents()->GetRenderViewHost(); |
| CHECK(rvh); |
| return rvh; |
| } |
| |
| RenderWidgetHostImpl* GetRenderWidgetHost() const { |
| RenderWidgetHostImpl* const rwh = RenderWidgetHostImpl::From( |
| shell()->web_contents()->GetRenderWidgetHostView()-> |
| GetRenderWidgetHost()); |
| CHECK(rwh); |
| return rwh; |
| } |
| |
| RenderWidgetHostViewBase* GetRenderWidgetHostView() const { |
| return static_cast<RenderWidgetHostViewBase*>( |
| GetRenderViewHost()->GetWidget()->GetView()); |
| } |
| |
| // Callback when using CopyFromSurface() API. |
| void FinishCopyFromSurface(const base::Closure& quit_closure, |
| const SkBitmap& bitmap) { |
| ++callback_invoke_count_; |
| if (!bitmap.drawsNothing()) |
| ++frames_captured_; |
| if (!quit_closure.is_null()) |
| quit_closure.Run(); |
| } |
| |
| protected: |
| // Waits until the source is available for copying. |
| void WaitForCopySourceReady() { |
| while (!GetRenderWidgetHostView()->IsSurfaceAvailableForCopy()) |
| GiveItSomeTime(); |
| } |
| |
| // Run the current message loop for a short time without unwinding the current |
| // call stack. |
| static void GiveItSomeTime() { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), |
| base::TimeDelta::FromMilliseconds(250)); |
| run_loop.Run(); |
| } |
| |
| private: |
| const gfx::Size frame_size_; |
| base::FilePath test_dir_; |
| int callback_invoke_count_; |
| int frames_captured_; |
| }; |
| |
| // Helps to ensure that a navigation is committed after a compositor frame was |
| // submitted by the renderer, but before corresponding ACK is sent back. |
| class CommitBeforeSwapAckSentHelper |
| : public DidCommitProvisionalLoadInterceptor { |
| public: |
| explicit CommitBeforeSwapAckSentHelper( |
| WebContents* web_contents, |
| RenderFrameSubmissionObserver* frame_observer) |
| : DidCommitProvisionalLoadInterceptor(web_contents), |
| frame_observer_(frame_observer) {} |
| |
| private: |
| // DidCommitProvisionalLoadInterceptor: |
| void WillDispatchDidCommitProvisionalLoad( |
| RenderFrameHost* render_frame_host, |
| ::FrameHostMsg_DidCommitProvisionalLoad_Params* params, |
| service_manager::mojom::InterfaceProviderRequest* |
| interface_provider_request) override { |
| base::MessageLoop::ScopedNestableTaskAllower allow( |
| base::MessageLoop::current()); |
| frame_observer_->WaitForAnyFrameSubmission(); |
| } |
| |
| // Not owned. |
| RenderFrameSubmissionObserver* const frame_observer_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CommitBeforeSwapAckSentHelper); |
| }; |
| |
| class RenderWidgetHostViewBrowserTestBase : public ContentBrowserTest { |
| public: |
| ~RenderWidgetHostViewBrowserTestBase() override {} |
| |
| protected: |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewBrowserTestBase, |
| CompositorWorksWhenReusingRenderer) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| auto* web_contents = shell()->web_contents(); |
| // Load a page that draws new frames infinitely. |
| NavigateToURL(shell(), |
| embedded_test_server()->GetURL("/page_with_animation.html")); |
| std::unique_ptr<RenderFrameSubmissionObserver> frame_observer( |
| std::make_unique<RenderFrameSubmissionObserver>(web_contents)); |
| |
| // Open a new page in the same renderer to keep it alive. |
| WebContents::CreateParams new_contents_params( |
| web_contents->GetBrowserContext(), web_contents->GetSiteInstance()); |
| std::unique_ptr<WebContents> new_web_contents( |
| WebContents::Create(new_contents_params)); |
| |
| new_web_contents->GetController().LoadURLWithParams( |
| NavigationController::LoadURLParams(GURL(url::kAboutBlankURL))); |
| EXPECT_TRUE(WaitForLoadStop(new_web_contents.get())); |
| |
| // Start a cross-process navigation. |
| shell()->LoadURL(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| |
| // When the navigation is about to commit, wait for the next frame to be |
| // submitted by the renderer before proceeding with page load. |
| { |
| CommitBeforeSwapAckSentHelper commit_helper(web_contents, |
| frame_observer.get()); |
| EXPECT_TRUE(WaitForLoadStop(web_contents)); |
| EXPECT_NE(web_contents->GetMainFrame()->GetProcess(), |
| new_web_contents->GetMainFrame()->GetProcess()); |
| } |
| |
| // Go back and verify that the renderer continues to draw new frames. |
| shell()->GoBackOrForward(-1); |
| // Stop observing before we destroy |web_contents| in WaitForLoadStop. |
| frame_observer.reset(); |
| EXPECT_TRUE(WaitForLoadStop(web_contents)); |
| EXPECT_EQ(web_contents->GetMainFrame()->GetProcess(), |
| new_web_contents->GetMainFrame()->GetProcess()); |
| MainThreadFrameObserver observer( |
| web_contents->GetRenderViewHost()->GetWidget()); |
| for (int i = 0; i < 5; ++i) |
| observer.Wait(); |
| } |
| |
| enum CompositingMode { |
| GL_COMPOSITING, |
| SOFTWARE_COMPOSITING, |
| }; |
| |
| class CompositingRenderWidgetHostViewBrowserTest |
| : public RenderWidgetHostViewBrowserTest, |
| public testing::WithParamInterface<CompositingMode> { |
| public: |
| CompositingRenderWidgetHostViewBrowserTest() |
| : compositing_mode_(GetParam()) {} |
| |
| void SetUp() override { |
| if (compositing_mode_ == SOFTWARE_COMPOSITING) |
| UseSoftwareCompositing(); |
| EnablePixelOutput(); |
| RenderWidgetHostViewBrowserTest::SetUp(); |
| } |
| |
| virtual GURL TestUrl() { |
| return net::FilePathToFileURL( |
| test_dir().AppendASCII("rwhv_compositing_animation.html")); |
| } |
| |
| bool SetUpSourceSurface(const char* wait_message) override { |
| content::DOMMessageQueue message_queue; |
| NavigateToURL(shell(), TestUrl()); |
| if (wait_message != nullptr) { |
| std::string result(wait_message); |
| if (!message_queue.WaitForMessage(&result)) { |
| EXPECT_TRUE(false) << "WaitForMessage " << result << " failed."; |
| return false; |
| } |
| } |
| |
| // A frame might not be available yet. So, wait for it. |
| WaitForCopySourceReady(); |
| return true; |
| } |
| |
| private: |
| const CompositingMode compositing_mode_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CompositingRenderWidgetHostViewBrowserTest); |
| }; |
| |
| // Disable tests for Android as it has an incomplete implementation. |
| #if !defined(OS_ANDROID) |
| |
| // The CopyFromSurface() API should work on all platforms when compositing is |
| // enabled. |
| IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTest, |
| CopyFromSurface) { |
| SET_UP_SURFACE_OR_PASS_TEST(nullptr); |
| |
| // Repeatedly call CopyFromSurface() since, on some platforms (e.g., Windows), |
| // the operation will fail until the first "present" has been made. |
| int count_attempts = 0; |
| while (true) { |
| ++count_attempts; |
| base::RunLoop run_loop; |
| GetRenderWidgetHostView()->CopyFromSurface( |
| gfx::Rect(), frame_size(), |
| base::BindOnce(&RenderWidgetHostViewBrowserTest::FinishCopyFromSurface, |
| base::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| |
| if (frames_captured()) |
| break; |
| else |
| GiveItSomeTime(); |
| } |
| |
| EXPECT_EQ(count_attempts, callback_invoke_count()); |
| EXPECT_EQ(1, frames_captured()); |
| } |
| |
| // Tests that the callback passed to CopyFromSurface is always called, even |
| // when the RenderWidgetHostView is deleting in the middle of an async copy. |
| // |
| // TODO(miu): On some bots (e.g., ChromeOS and Cast Shell), this test fails |
| // because the RunLoop quits before its QuitClosure() is run. This is because |
| // the call to WebContents::Close() leads to something that makes the current |
| // thread's RunLoop::Delegate constantly report "should quit." We'll need to |
| // find a better way of testing this functionality. |
| IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTest, |
| DISABLED_CopyFromSurface_CallbackDespiteDelete) { |
| SET_UP_SURFACE_OR_PASS_TEST(nullptr); |
| |
| base::RunLoop run_loop; |
| GetRenderWidgetHostView()->CopyFromSurface( |
| gfx::Rect(), frame_size(), |
| base::BindOnce(&RenderWidgetHostViewBrowserTest::FinishCopyFromSurface, |
| base::Unretained(this), run_loop.QuitClosure())); |
| shell()->web_contents()->Close(); |
| run_loop.Run(); |
| |
| EXPECT_EQ(1, callback_invoke_count()); |
| } |
| |
| class CompositingRenderWidgetHostViewBrowserTestTabCapture |
| : public CompositingRenderWidgetHostViewBrowserTest { |
| public: |
| CompositingRenderWidgetHostViewBrowserTestTabCapture() |
| : readback_result_(READBACK_NO_RESPONSE), |
| allowable_error_(0), |
| test_url_("data:text/html,<!doctype html>") {} |
| |
| void VerifyResult(base::OnceClosure quit_callback, const SkBitmap& bitmap) { |
| if (bitmap.drawsNothing()) { |
| readback_result_ = READBACK_FAILED; |
| std::move(quit_callback).Run(); |
| return; |
| } |
| readback_result_ = READBACK_SUCCESS; |
| |
| // Check that the |bitmap| contains cyan and/or yellow pixels. This is |
| // needed because the compositor will read back "blank" frames until the |
| // first frame from the renderer is composited. See comments in |
| // PerformTestWithLeftRightRects() for more details about eliminating test |
| // flakiness. |
| bool contains_a_test_color = false; |
| for (int i = 0; i < bitmap.width(); ++i) { |
| for (int j = 0; j < bitmap.height(); ++j) { |
| if (!exclude_rect_.IsEmpty() && exclude_rect_.Contains(i, j)) |
| continue; |
| |
| const unsigned high_threshold = 0xff - allowable_error_; |
| const unsigned low_threshold = 0x00 + allowable_error_; |
| const SkColor color = bitmap.getColor(i, j); |
| const bool is_cyan = SkColorGetR(color) <= low_threshold && |
| SkColorGetG(color) >= high_threshold && |
| SkColorGetB(color) >= high_threshold; |
| const bool is_yellow = SkColorGetR(color) >= high_threshold && |
| SkColorGetG(color) >= high_threshold && |
| SkColorGetB(color) <= low_threshold; |
| if (is_cyan || is_yellow) { |
| contains_a_test_color = true; |
| break; |
| } |
| } |
| } |
| if (!contains_a_test_color) { |
| readback_result_ = READBACK_NO_TEST_COLORS; |
| std::move(quit_callback).Run(); |
| return; |
| } |
| |
| // Compare the readback |bitmap| to the |expected_bitmap|, pixel-by-pixel. |
| const SkBitmap& expected_bitmap = |
| expected_copy_from_compositing_surface_bitmap_; |
| EXPECT_EQ(expected_bitmap.width(), bitmap.width()); |
| EXPECT_EQ(expected_bitmap.height(), bitmap.height()); |
| if (expected_bitmap.width() != bitmap.width() || |
| expected_bitmap.height() != bitmap.height()) { |
| readback_result_ = READBACK_INCORRECT_RESULT_SIZE; |
| std::move(quit_callback).Run(); |
| return; |
| } |
| EXPECT_EQ(expected_bitmap.colorType(), bitmap.colorType()); |
| int fails = 0; |
| for (int i = 0; i < bitmap.width() && fails < 10; ++i) { |
| for (int j = 0; j < bitmap.height() && fails < 10; ++j) { |
| if (!exclude_rect_.IsEmpty() && exclude_rect_.Contains(i, j)) |
| continue; |
| |
| SkColor expected_color = expected_bitmap.getColor(i, j); |
| SkColor color = bitmap.getColor(i, j); |
| int expected_alpha = SkColorGetA(expected_color); |
| int alpha = SkColorGetA(color); |
| int expected_red = SkColorGetR(expected_color); |
| int red = SkColorGetR(color); |
| int expected_green = SkColorGetG(expected_color); |
| int green = SkColorGetG(color); |
| int expected_blue = SkColorGetB(expected_color); |
| int blue = SkColorGetB(color); |
| EXPECT_NEAR(expected_alpha, alpha, allowable_error_) |
| << "expected_color: " << std::hex << expected_color |
| << " color: " << color |
| << " Failed at " << std::dec << i << ", " << j |
| << " Failure " << ++fails; |
| EXPECT_NEAR(expected_red, red, allowable_error_) |
| << "expected_color: " << std::hex << expected_color |
| << " color: " << color |
| << " Failed at " << std::dec << i << ", " << j |
| << " Failure " << ++fails; |
| EXPECT_NEAR(expected_green, green, allowable_error_) |
| << "expected_color: " << std::hex << expected_color |
| << " color: " << color |
| << " Failed at " << std::dec << i << ", " << j |
| << " Failure " << ++fails; |
| EXPECT_NEAR(expected_blue, blue, allowable_error_) |
| << "expected_color: " << std::hex << expected_color |
| << " color: " << color |
| << " Failed at " << std::dec << i << ", " << j |
| << " Failure " << ++fails; |
| } |
| } |
| EXPECT_LT(fails, 10); |
| |
| std::move(quit_callback).Run(); |
| } |
| |
| void SetAllowableError(int amount) { allowable_error_ = amount; } |
| void SetExcludeRect(gfx::Rect exclude) { exclude_rect_ = exclude; } |
| |
| GURL TestUrl() override { return GURL(test_url_); } |
| |
| void SetTestUrl(const std::string& url) { test_url_ = url; } |
| |
| // Loads a page two boxes side-by-side, each half the width of |
| // |html_rect_size|, and with different background colors. The test then |
| // copies from |copy_rect| region of the page into a bitmap of size |
| // |output_size|, and examines the resulting bitmap. |
| // Note that |output_size| may not have the same size as |copy_rect| (e.g. |
| // when the output is scaled). |
| void PerformTestWithLeftRightRects(const gfx::Size& html_rect_size, |
| const gfx::Rect& copy_rect, |
| const gfx::Size& output_size) { |
| const gfx::Size box_size(html_rect_size.width() / 2, |
| html_rect_size.height()); |
| SetTestUrl(base::StringPrintf( |
| "data:text/html,<!doctype html>" |
| "<div class='left'>" |
| " <div class='right'></div>" |
| "</div>" |
| "<style>" |
| "body { padding: 0; margin: 0; }" |
| ".left { position: absolute;" |
| " background: #0ff;" |
| " width: %dpx;" |
| " height: %dpx;" |
| "}" |
| ".right { position: absolute;" |
| " left: %dpx;" |
| " background: #ff0;" |
| " width: %dpx;" |
| " height: %dpx;" |
| "}" |
| "</style>" |
| "<script>" |
| " domAutomationController.send(\"DONE\");" |
| "</script>", |
| box_size.width(), |
| box_size.height(), |
| box_size.width(), |
| box_size.width(), |
| box_size.height())); |
| |
| SET_UP_SURFACE_OR_PASS_TEST("\"DONE\""); |
| if (!ShouldContinueAfterTestURLLoad()) |
| return; |
| |
| RenderWidgetHostViewBase* rwhv = GetRenderWidgetHostView(); |
| |
| SetupLeftRightBitmap(output_size, |
| &expected_copy_from_compositing_surface_bitmap_); |
| |
| // The page is loaded in the renderer. Request frames from the renderer |
| // until readback succeeds. When readback succeeds, the resulting |
| // SkBitmap is examined to ensure it matches the expected result. |
| // This loop is needed because: |
| // 1. Painting/Compositing is not synchronous with the Javascript engine, |
| // and so the "DONE" signal above could be received before the renderer |
| // provides a frame with the expected content. http://crbug.com/405282 |
| // 2. Avoiding test flakiness: On some platforms, the readback operation |
| // is allowed to transiently fail. The purpose of these tests is to |
| // confirm correct cropping/scaling behavior; and not that every |
| // readback must succeed. http://crbug.com/444237 |
| int attempt_count = 0; |
| do { |
| // Wait a little before retrying again. This gives the most up-to-date |
| // frame a chance to propagate from the renderer to the compositor. |
| if (attempt_count > 0) |
| GiveItSomeTime(); |
| ++attempt_count; |
| |
| // Request readback. The callbacks will examine the pixels in the |
| // SkBitmap result if readback was successful. |
| readback_result_ = READBACK_NO_RESPONSE; |
| // Skia rendering can cause color differences, particularly in the |
| // middle two columns. |
| SetAllowableError(2); |
| SetExcludeRect( |
| gfx::Rect(output_size.width() / 2 - 1, 0, 2, output_size.height())); |
| |
| base::RunLoop run_loop; |
| rwhv->CopyFromSurface( |
| copy_rect, output_size, |
| base::BindOnce(&CompositingRenderWidgetHostViewBrowserTestTabCapture:: |
| VerifyResult, |
| base::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| |
| // If the readback operation did not provide a frame, log the reason |
| // to aid in future debugging. This information will also help determine |
| // whether the implementation is broken, or a test bot is in a bad state. |
| // clang-format off |
| switch (readback_result_) { |
| case READBACK_SUCCESS: |
| break; |
| #define CASE_LOG_READBACK_WARNING(enum_value) \ |
| case enum_value: \ |
| LOG(WARNING) << "Readback attempt failed (attempt #" \ |
| << attempt_count << "). Reason: " #enum_value; \ |
| break |
| CASE_LOG_READBACK_WARNING(READBACK_FAILED); |
| CASE_LOG_READBACK_WARNING(READBACK_NO_TEST_COLORS); |
| CASE_LOG_READBACK_WARNING(READBACK_INCORRECT_RESULT_SIZE); |
| default: |
| LOG(ERROR) |
| << "Invalid readback response value: " << readback_result_; |
| NOTREACHED(); |
| } |
| // clang-format on |
| } while (readback_result_ != READBACK_SUCCESS && |
| !testing::Test::HasFailure()); |
| } |
| |
| // Sets up |bitmap| to have size |copy_size|. It floods the left half with |
| // #0ff and the right half with #ff0. |
| void SetupLeftRightBitmap(const gfx::Size& copy_size, SkBitmap* bitmap) { |
| bitmap->allocN32Pixels(copy_size.width(), copy_size.height()); |
| // Left half is #0ff. |
| bitmap->eraseARGB(255, 0, 255, 255); |
| // Right half is #ff0. |
| for (int i = 0; i < copy_size.width() / 2; ++i) { |
| for (int j = 0; j < copy_size.height(); ++j) { |
| *(bitmap->getAddr32(copy_size.width() / 2 + i, j)) = |
| SkColorSetARGB(255, 255, 255, 0); |
| } |
| } |
| } |
| |
| protected: |
| // An enum to distinguish between reasons for result verify failures. |
| enum ReadbackResult { |
| READBACK_NO_RESPONSE, |
| READBACK_SUCCESS, |
| READBACK_FAILED, |
| READBACK_NO_TEST_COLORS, |
| READBACK_INCORRECT_RESULT_SIZE, |
| }; |
| |
| virtual bool ShouldContinueAfterTestURLLoad() { |
| return true; |
| } |
| |
| private: |
| ReadbackResult readback_result_; |
| SkBitmap expected_copy_from_compositing_surface_bitmap_; |
| int allowable_error_; |
| gfx::Rect exclude_rect_; |
| std::string test_url_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture, |
| CopyFromSurface_Origin_Unscaled) { |
| gfx::Rect copy_rect(400, 300); |
| gfx::Size output_size = copy_rect.size(); |
| gfx::Size html_rect_size(400, 300); |
| PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture, |
| CopyFromSurface_Origin_Scaled) { |
| gfx::Rect copy_rect(400, 300); |
| gfx::Size output_size(200, 100); |
| gfx::Size html_rect_size(400, 300); |
| PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture, |
| CopyFromSurface_Cropped_Unscaled) { |
| // Grab 60x60 pixels from the center of the tab contents. |
| gfx::Rect copy_rect(400, 300); |
| copy_rect = gfx::Rect(copy_rect.CenterPoint() - gfx::Vector2d(30, 30), |
| gfx::Size(60, 60)); |
| gfx::Size output_size = copy_rect.size(); |
| gfx::Size html_rect_size(400, 300); |
| PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture, |
| CopyFromSurface_Cropped_Scaled) { |
| // Grab 60x60 pixels from the center of the tab contents. |
| gfx::Rect copy_rect(400, 300); |
| copy_rect = gfx::Rect(copy_rect.CenterPoint() - gfx::Vector2d(30, 30), |
| gfx::Size(60, 60)); |
| gfx::Size output_size(20, 10); |
| gfx::Size html_rect_size(400, 300); |
| PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size); |
| } |
| |
| class CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI |
| : public CompositingRenderWidgetHostViewBrowserTestTabCapture { |
| public: |
| CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI() {} |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* cmd) override { |
| CompositingRenderWidgetHostViewBrowserTestTabCapture::SetUpCommandLine(cmd); |
| cmd->AppendSwitchASCII(switches::kForceDeviceScaleFactor, |
| base::StringPrintf("%f", scale())); |
| } |
| |
| bool ShouldContinueAfterTestURLLoad() override { |
| // Short-circuit a pass for platforms where setting up high-DPI fails. |
| const float actual_scale_factor = |
| GetScaleFactorForView(GetRenderWidgetHostView()); |
| if (actual_scale_factor != scale()) { |
| LOG(WARNING) << "Blindly passing this test; unable to force device scale " |
| << "factor: seems to be " << actual_scale_factor |
| << " but expected " << scale(); |
| return false; |
| } |
| VLOG(1) << ("Successfully forced device scale factor. Moving forward with " |
| "this test! :-)"); |
| return true; |
| } |
| |
| static float scale() { return 2.0f; } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN( |
| CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI); |
| }; |
| |
| // NineImagePainter implementation crashes the process on Windows when this |
| // content_browsertest forces a device scale factor. http://crbug.com/399349 |
| #if defined(OS_WIN) |
| #define MAYBE_CopyToBitmap_EntireRegion DISABLED_CopyToBitmap_EntireRegion |
| #define MAYBE_CopyToBitmap_CenterRegion DISABLED_CopyToBitmap_CenterRegion |
| #define MAYBE_CopyToBitmap_ScaledResult DISABLED_CopyToBitmap_ScaledResult |
| #else |
| #define MAYBE_CopyToBitmap_EntireRegion CopyToBitmap_EntireRegion |
| #define MAYBE_CopyToBitmap_CenterRegion CopyToBitmap_CenterRegion |
| #define MAYBE_CopyToBitmap_ScaledResult CopyToBitmap_ScaledResult |
| #endif |
| |
| IN_PROC_BROWSER_TEST_P( |
| CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI, |
| MAYBE_CopyToBitmap_EntireRegion) { |
| gfx::Size html_rect_size(200, 150); |
| gfx::Rect copy_rect(200, 150); |
| // Scale the output size so that, internally, scaling is not occurring. |
| gfx::Size output_size = gfx::ScaleToRoundedSize(copy_rect.size(), scale()); |
| PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI, |
| MAYBE_CopyToBitmap_CenterRegion) { |
| gfx::Size html_rect_size(200, 150); |
| // Grab 90x60 pixels from the center of the tab contents. |
| gfx::Rect copy_rect = |
| gfx::Rect(gfx::Rect(html_rect_size).CenterPoint() - gfx::Vector2d(45, 30), |
| gfx::Size(90, 60)); |
| // Scale the output size so that, internally, scaling is not occurring. |
| gfx::Size output_size = gfx::ScaleToRoundedSize(copy_rect.size(), scale()); |
| PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI, |
| MAYBE_CopyToBitmap_ScaledResult) { |
| gfx::Size html_rect_size(200, 100); |
| gfx::Rect copy_rect(200, 100); |
| // Output is being down-scaled since output_size is in phyiscal pixels. |
| gfx::Size output_size(200, 100); |
| PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size); |
| } |
| |
| class CompositingRenderWidgetHostViewBrowserTestHiDPI |
| : public CompositingRenderWidgetHostViewBrowserTest { |
| public: |
| CompositingRenderWidgetHostViewBrowserTestHiDPI() {} |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* cmd) override { |
| CompositingRenderWidgetHostViewBrowserTest::SetUpCommandLine(cmd); |
| cmd->AppendSwitchASCII(switches::kForceDeviceScaleFactor, |
| base::StringPrintf("%f", scale())); |
| } |
| |
| GURL TestUrl() override { return GURL(test_url_); } |
| |
| void SetTestUrl(const std::string& url) { test_url_ = url; } |
| |
| bool ShouldContinueAfterTestURLLoad() { |
| // Short-circuit a pass for platforms where setting up high-DPI fails. |
| const float actual_scale_factor = |
| GetScaleFactorForView(GetRenderWidgetHostView()); |
| if (actual_scale_factor != scale()) { |
| LOG(WARNING) << "Blindly passing this test; unable to force device scale " |
| << "factor: seems to be " << actual_scale_factor |
| << " but expected " << scale(); |
| return false; |
| } |
| VLOG(1) |
| << ("Successfully forced device scale factor. Moving forward with " |
| "this test! :-)"); |
| return true; |
| } |
| |
| static float scale() { return 2.0f; } |
| |
| private: |
| std::string test_url_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CompositingRenderWidgetHostViewBrowserTestHiDPI); |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestHiDPI, |
| ScrollOffset) { |
| const int kContentHeight = 2000; |
| const int kScrollAmount = 100; |
| |
| SetTestUrl( |
| base::StringPrintf("data:text/html,<!doctype html>" |
| "<div class='box'></div>" |
| "<style>" |
| "body { padding: 0; margin: 0; }" |
| ".box { position: absolute;" |
| " background: #0ff;" |
| " width: 100%%;" |
| " height: %dpx;" |
| "}" |
| "</style>" |
| "<script>" |
| " addEventListener(\"scroll\", function() {" |
| " domAutomationController.send(\"DONE\"); });" |
| " window.scrollTo(0, %d);" |
| "</script>", |
| kContentHeight, kScrollAmount)); |
| |
| SET_UP_SURFACE_OR_PASS_TEST("\"DONE\""); |
| if (!ShouldContinueAfterTestURLLoad()) |
| return; |
| |
| RenderWidgetHostViewBase* rwhv = GetRenderWidgetHostView(); |
| gfx::Vector2dF scroll_offset = rwhv->GetLastScrollOffset(); |
| |
| EXPECT_EQ(scroll_offset.x(), 0); |
| EXPECT_EQ(scroll_offset.y(), kScrollAmount); |
| } |
| |
| #if defined(OS_CHROMEOS) |
| // On ChromeOS there is no software compositing. |
| static const auto kTestCompositingModes = testing::Values(GL_COMPOSITING); |
| #else |
| static const auto kTestCompositingModes = |
| testing::Values(GL_COMPOSITING, SOFTWARE_COMPOSITING); |
| #endif |
| |
| INSTANTIATE_TEST_CASE_P(GLAndSoftwareCompositing, |
| CompositingRenderWidgetHostViewBrowserTest, |
| kTestCompositingModes); |
| INSTANTIATE_TEST_CASE_P(GLAndSoftwareCompositing, |
| CompositingRenderWidgetHostViewBrowserTestTabCapture, |
| kTestCompositingModes); |
| INSTANTIATE_TEST_CASE_P( |
| GLAndSoftwareCompositing, |
| CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI, |
| kTestCompositingModes); |
| INSTANTIATE_TEST_CASE_P(GLAndSoftwareCompositing, |
| CompositingRenderWidgetHostViewBrowserTestHiDPI, |
| kTestCompositingModes); |
| |
| #endif // !defined(OS_ANDROID) |
| |
| } // namespace |
| } // namespace content |