1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/file_util.h"
7 #include "base/files/file.h"
8 #include "base/files/file_enumerator.h"
10 #include "base/path_service.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_window.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model.h"
18 #include "chrome/common/chrome_paths.h"
19 #include "chrome/test/base/in_process_browser_test.h"
20 #include "chrome/test/base/ui_test_utils.h"
21 #include "content/public/browser/navigation_controller.h"
22 #include "content/public/browser/notification_observer.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/common/content_switches.h"
26 #include "content/public/test/browser_test_utils.h"
27 #include "net/test/embedded_test_server/embedded_test_server.h"
28 #include "third_party/skia/include/core/SkBitmap.h"
29 #include "ui/base/clipboard/clipboard.h"
30 #include "ui/gfx/codec/png_codec.h"
31 #include "ui/gfx/screen.h"
33 using content::NavigationController
;
34 using content::WebContents
;
36 // Note: All tests in here require the internal PDF plugin, so they're disabled
37 // in non-official builds. We still compile them though, to prevent bitrot.
41 // Include things like browser frame and scrollbar and make sure we're bigger
42 // than the test pdf document.
43 static const int kBrowserWidth
= 1000;
44 static const int kBrowserHeight
= 600;
46 class PDFBrowserTest
: public InProcessBrowserTest
,
47 public testing::WithParamInterface
<int>,
48 public content::NotificationObserver
{
51 : snapshot_different_(true),
52 next_dummy_search_value_(0),
53 load_stop_notification_count_(0) {
54 base::FilePath src_dir
;
55 EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT
, &src_dir
));
56 pdf_test_server_
.ServeFilesFromDirectory(src_dir
.AppendASCII(
57 "chrome/test/data/pdf_private"));
61 // Use our own TestServer so that we can serve files from the pdf directory.
62 net::test_server::EmbeddedTestServer
* pdf_test_server() {
63 return &pdf_test_server_
;
66 int load_stop_notification_count() const {
67 return load_stop_notification_count_
;
71 // Make sure to set the window size before rendering, as otherwise rendering
72 // to a smaller window and then expanding leads to slight anti-aliasing
73 // differences of the text and the pixel comparison fails.
74 gfx::Rect
bounds(gfx::Rect(0, 0, kBrowserWidth
, kBrowserHeight
));
75 gfx::Rect screen_bounds
=
76 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().bounds();
77 ASSERT_GT(screen_bounds
.width(), kBrowserWidth
);
78 ASSERT_GT(screen_bounds
.height(), kBrowserHeight
);
79 browser()->window()->SetBounds(bounds
);
81 GURL
url(ui_test_utils::GetTestUrl(
82 base::FilePath(FILE_PATH_LITERAL("pdf_private")),
83 base::FilePath(FILE_PATH_LITERAL("pdf_browsertest.pdf"))));
84 ui_test_utils::NavigateToURL(browser(), url
);
87 bool VerifySnapshot(const std::string
& expected_filename
) {
88 snapshot_different_
= true;
89 expected_filename_
= expected_filename
;
90 WebContents
* web_contents
=
91 browser()->tab_strip_model()->GetActiveWebContents();
94 content::RenderWidgetHost
* rwh
= web_contents
->GetRenderViewHost();
95 rwh
->CopyFromBackingStore(
98 base::Bind(&PDFBrowserTest::CopyFromBackingStoreCallback
, this),
101 content::RunMessageLoop();
103 if (snapshot_different_
) {
104 LOG(INFO
) << "Rendering didn't match, see result " <<
105 snapshot_filename_
.value();
107 return !snapshot_different_
;
110 void WaitForResponse() {
111 // Even if the plugin has loaded the data or scrolled, because of how
112 // pepper painting works, we might not have the data. One way to force this
113 // to be flushed is to do a find operation, since on this two-page test
114 // document, it'll wait for us to flush the renderer message loop twice and
115 // also the browser's once, at which point we're guaranteed to have updated
116 // the backingstore. Hacky, but it works.
117 // Note that we need to change the text each time, because if we don't the
118 // renderer code will think the second message is to go to next result, but
119 // there are none so the plugin will assert.
121 base::string16 query
= base::UTF8ToUTF16(
122 std::string("xyzxyz" + base::IntToString(next_dummy_search_value_
++)));
123 ASSERT_EQ(0, ui_test_utils::FindInPage(
124 browser()->tab_strip_model()->GetActiveWebContents(),
125 query
, true, false, NULL
, NULL
));
129 void CopyFromBackingStoreCallback(bool success
, const SkBitmap
& bitmap
) {
130 base::MessageLoopForUI::current()->Quit();
131 ASSERT_EQ(success
, true);
132 base::FilePath reference
= ui_test_utils::GetTestFilePath(
133 base::FilePath(FILE_PATH_LITERAL("pdf_private")),
134 base::FilePath().AppendASCII(expected_filename_
));
135 base::File::Info info
;
136 ASSERT_TRUE(base::GetFileInfo(reference
, &info
));
137 int size
= static_cast<size_t>(info
.size
);
138 scoped_ptr
<char[]> data(new char[size
]);
139 ASSERT_EQ(size
, base::ReadFile(reference
, data
.get(), size
));
142 std::vector
<unsigned char> decoded
;
143 ASSERT_TRUE(gfx::PNGCodec::Decode(
144 reinterpret_cast<unsigned char*>(data
.get()), size
,
145 gfx::PNGCodec::FORMAT_BGRA
, &decoded
, &w
, &h
));
146 int32
* ref_pixels
= reinterpret_cast<int32
*>(&decoded
[0]);
148 int32
* pixels
= static_cast<int32
*>(bitmap
.getPixels());
150 // Get the background color, and use it to figure out the x-offsets in
151 // each image. The reason is that depending on the theme in the OS, the
152 // same browser width can lead to slightly different plugin sizes, so the
153 // pdf content will start at different x offsets.
154 // Also note that the images we saved are cut off before the scrollbar, as
155 // that'll change depending on the theme, and also cut off vertically so
156 // that the ui controls don't show up, as those fade-in and so the timing
157 // will affect their transparency.
158 int32 bg_color
= ref_pixels
[0];
159 int ref_x_offset
, snapshot_x_offset
;
160 for (ref_x_offset
= 0; ref_x_offset
< w
; ++ref_x_offset
) {
161 if (ref_pixels
[ref_x_offset
] != bg_color
)
165 for (snapshot_x_offset
= 0; snapshot_x_offset
< bitmap
.width();
166 ++snapshot_x_offset
) {
167 if (pixels
[snapshot_x_offset
] != bg_color
)
171 int x_max
= std::min(
172 w
- ref_x_offset
, bitmap
.width() - snapshot_x_offset
);
173 int y_max
= std::min(h
, bitmap
.height());
174 int stride
= bitmap
.rowBytes();
175 snapshot_different_
= false;
176 for (int y
= 0; y
< y_max
&& !snapshot_different_
; ++y
) {
177 for (int x
= 0; x
< x_max
&& !snapshot_different_
; ++x
) {
178 if (pixels
[y
* stride
/ sizeof(int32
) + x
+ snapshot_x_offset
] !=
179 ref_pixels
[y
* w
+ x
+ ref_x_offset
])
180 snapshot_different_
= true;
184 if (snapshot_different_
) {
185 std::vector
<unsigned char> png_data
;
186 gfx::PNGCodec::EncodeBGRASkBitmap(bitmap
, false, &png_data
);
187 if (base::CreateTemporaryFile(&snapshot_filename_
)) {
188 base::WriteFile(snapshot_filename_
,
189 reinterpret_cast<char*>(&png_data
[0]), png_data
.size());
194 // content::NotificationObserver
195 virtual void Observe(int type
,
196 const content::NotificationSource
& source
,
197 const content::NotificationDetails
& details
) OVERRIDE
{
198 if (type
== content::NOTIFICATION_LOAD_STOP
) {
199 load_stop_notification_count_
++;
203 // InProcessBrowserTest
204 virtual void SetUpCommandLine(base::CommandLine
* command_line
) OVERRIDE
{
205 #if defined(OS_LINUX)
206 // Calling RenderWidgetHost::CopyFromBackingStore() with the GPU enabled
208 command_line
->AppendSwitch(switches::kDisableGpu
);
212 // True if the snapshot differed from the expected value.
213 bool snapshot_different_
;
214 // Internal variable used to synchronize to the renderer.
215 int next_dummy_search_value_
;
216 // The filename of the bitmap to compare the snapshot to.
217 std::string expected_filename_
;
218 // If the snapshot is different, holds the location where it's saved.
219 base::FilePath snapshot_filename_
;
220 // How many times we've seen chrome::LOAD_STOP.
221 int load_stop_notification_count_
;
223 net::test_server::EmbeddedTestServer pdf_test_server_
;
227 // Tests basic PDF rendering. This can be broken depending on bad merges with
228 // the vendor, so it's important that we have basic sanity checking.
229 #if defined(GOOGLE_CHROME_BUILD) && defined(OS_LINUX)
230 #define MAYBE_Basic Basic
232 #define MAYBE_Basic DISABLED_Basic
234 IN_PROC_BROWSER_TEST_F(PDFBrowserTest
, MAYBE_Basic
) {
235 ASSERT_NO_FATAL_FAILURE(Load());
236 ASSERT_NO_FATAL_FAILURE(WaitForResponse());
237 // OS X uses CoreText, and FreeType renders slightly different on Linux and
239 #if defined(OS_MACOSX)
240 // The bots render differently than locally, see http://crbug.com/142531.
241 ASSERT_TRUE(VerifySnapshot("pdf_browsertest_mac.png") ||
242 VerifySnapshot("pdf_browsertest_mac2.png"));
243 #elif defined(OS_LINUX)
244 ASSERT_TRUE(VerifySnapshot("pdf_browsertest_linux.png"));
246 ASSERT_TRUE(VerifySnapshot("pdf_browsertest.png"));
250 #if defined(GOOGLE_CHROME_BUILD) && (defined(OS_WIN) || defined(OS_LINUX))
251 #define MAYBE_Scroll Scroll
253 // TODO(thestig): http://crbug.com/79837, http://crbug.com/332778
254 #define MAYBE_Scroll DISABLED_Scroll
256 // Tests that scrolling works.
257 IN_PROC_BROWSER_TEST_F(PDFBrowserTest
, MAYBE_Scroll
) {
258 ASSERT_NO_FATAL_FAILURE(Load());
260 // We use wheel mouse event since that's the only one we can easily push to
261 // the renderer. There's no way to push a cross-platform keyboard event at
263 blink::WebMouseWheelEvent wheel_event
;
264 wheel_event
.type
= blink::WebInputEvent::MouseWheel
;
265 wheel_event
.deltaY
= -200;
266 wheel_event
.wheelTicksY
= -2;
267 WebContents
* web_contents
=
268 browser()->tab_strip_model()->GetActiveWebContents();
269 web_contents
->GetRenderViewHost()->ForwardWheelEvent(wheel_event
);
270 ASSERT_NO_FATAL_FAILURE(WaitForResponse());
273 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
274 browser()->tab_strip_model()->GetActiveWebContents(),
275 "window.domAutomationController.send(plugin.pageYOffset())",
277 ASSERT_GT(y_offset
, 0);
280 #if defined(GOOGLE_CHROME_BUILD) && (defined(OS_WIN) || defined(OS_LINUX))
281 #define MAYBE_FindAndCopy FindAndCopy
283 // TODO(thestig): http://crbug.com/79837, http://crbug.com/329912
284 #define MAYBE_FindAndCopy DISABLED_FindAndCopy
286 IN_PROC_BROWSER_TEST_F(PDFBrowserTest
, MAYBE_FindAndCopy
) {
287 ASSERT_NO_FATAL_FAILURE(Load());
288 // Verifies that find in page works.
289 ASSERT_EQ(3, ui_test_utils::FindInPage(
290 browser()->tab_strip_model()->GetActiveWebContents(),
291 base::UTF8ToUTF16("adipiscing"),
292 true, false, NULL
, NULL
));
294 // Verify that copying selected text works.
295 ui::Clipboard
* clipboard
= ui::Clipboard::GetForCurrentThread();
296 // Reset the clipboard first.
297 clipboard
->Clear(ui::CLIPBOARD_TYPE_COPY_PASTE
);
299 browser()->tab_strip_model()->GetActiveWebContents()->Copy();
300 ASSERT_NO_FATAL_FAILURE(WaitForResponse());
303 clipboard
->ReadAsciiText(ui::CLIPBOARD_TYPE_COPY_PASTE
, &text
);
304 ASSERT_EQ("adipiscing", text
);
307 const int kLoadingNumberOfParts
= 10;
309 // Tests that loading async pdfs works correctly (i.e. document fully loads).
310 // This also loads all documents that used to crash, to ensure we don't have
312 // If it flakes, reopen http://crbug.com/74548.
313 #if defined(GOOGLE_CHROME_BUILD)
314 #define MAYBE_Loading Loading
316 #define MAYBE_Loading DISABLED_Loading
318 IN_PROC_BROWSER_TEST_P(PDFBrowserTest
, MAYBE_Loading
) {
319 ASSERT_TRUE(pdf_test_server()->InitializeAndWaitUntilReady());
321 NavigationController
* controller
=
322 &(browser()->tab_strip_model()->GetActiveWebContents()->GetController());
323 content::NotificationRegistrar registrar
;
325 content::NOTIFICATION_LOAD_STOP
,
326 content::Source
<NavigationController
>(controller
));
327 std::string base_url
= std::string("/");
329 base::FileEnumerator
file_enumerator(
330 ui_test_utils::GetTestFilePath(
331 base::FilePath(FILE_PATH_LITERAL("pdf_private")), base::FilePath()),
333 base::FileEnumerator::FILES
,
334 FILE_PATH_LITERAL("*.pdf"));
335 for (base::FilePath file_path
= file_enumerator
.Next();
337 file_path
= file_enumerator
.Next()) {
338 std::string filename
= file_path
.BaseName().MaybeAsASCII();
339 ASSERT_FALSE(filename
.empty());
341 #if defined(OS_POSIX)
342 if (filename
== "sample.pdf")
343 continue; // Crashes on Mac and Linux. http://crbug.com/63549
346 // Split the test into smaller sub-tests. Each one only loads
348 if (static_cast<int>(base::Hash(filename
) % kLoadingNumberOfParts
) !=
353 LOG(WARNING
) << "PDFBrowserTest.Loading: " << filename
;
355 GURL url
= pdf_test_server()->GetURL(base_url
+ filename
);
356 ui_test_utils::NavigateToURL(browser(), url
);
359 int last_count
= load_stop_notification_count();
360 // We might get extraneous chrome::LOAD_STOP notifications when
361 // doing async loading. This happens when the first loader is cancelled
362 // and before creating a byte-range request loader.
363 bool complete
= false;
364 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
365 browser()->tab_strip_model()->GetActiveWebContents(),
366 "window.domAutomationController.send(plugin.documentLoadComplete())",
371 // Check if the LOAD_STOP notification could have come while we run a
372 // nested message loop for the JS call.
373 if (last_count
!= load_stop_notification_count())
375 content::WaitForLoadStop(
376 browser()->tab_strip_model()->GetActiveWebContents());
381 INSTANTIATE_TEST_CASE_P(PDFTestFiles
,
383 testing::Range(0, kLoadingNumberOfParts
));
385 #if defined(GOOGLE_CHROME_BUILD) && (defined(OS_WIN) || defined(OS_LINUX))
386 #define MAYBE_Action Action
388 // http://crbug.com/315160
389 #define MAYBE_Action DISABLED_Action
391 IN_PROC_BROWSER_TEST_F(PDFBrowserTest
, MAYBE_Action
) {
392 ASSERT_NO_FATAL_FAILURE(Load());
394 ASSERT_TRUE(content::ExecuteScript(
395 browser()->tab_strip_model()->GetActiveWebContents(),
396 "document.getElementsByName('plugin')[0].fitToHeight();"));
398 std::string zoom1
, zoom2
;
399 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
400 browser()->tab_strip_model()->GetActiveWebContents(),
401 "window.domAutomationController.send("
402 " document.getElementsByName('plugin')[0].getZoomLevel().toString())",
405 ASSERT_TRUE(content::ExecuteScript(
406 browser()->tab_strip_model()->GetActiveWebContents(),
407 "document.getElementsByName('plugin')[0].fitToWidth();"));
409 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
410 browser()->tab_strip_model()->GetActiveWebContents(),
411 "window.domAutomationController.send("
412 " document.getElementsByName('plugin')[0].getZoomLevel().toString())",
414 ASSERT_NE(zoom1
, zoom2
);
417 #if defined(GOOGLE_CHROME_BUILD) && defined(OS_LINUX)
418 #define MAYBE_OnLoadAndReload OnLoadAndReload
420 // Flaky as per http://crbug.com/74549.
421 #define MAYBE_OnLoadAndReload DISABLED_OnLoadAndReload
423 IN_PROC_BROWSER_TEST_F(PDFBrowserTest
, MAYBE_OnLoadAndReload
) {
424 ASSERT_TRUE(pdf_test_server()->InitializeAndWaitUntilReady());
426 GURL url
= pdf_test_server()->GetURL("/onload_reload.html");
427 ui_test_utils::NavigateToURL(browser(), url
);
428 WebContents
* contents
= browser()->tab_strip_model()->GetActiveWebContents();
430 content::WindowedNotificationObserver
observer(
431 content::NOTIFICATION_LOAD_STOP
,
432 content::Source
<NavigationController
>(
433 &contents
->GetController()));
434 ASSERT_TRUE(content::ExecuteScript(
435 browser()->tab_strip_model()->GetActiveWebContents(),
439 ASSERT_EQ("success", contents
->GetURL().query());