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.
4
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"
9 #include "base/hash.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"
32
33 using content::NavigationController;
34 using content::WebContents;
35
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.
38
39 namespace {
40
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;
45
46 class PDFBrowserTest : public InProcessBrowserTest,
47 public testing::WithParamInterface<int>,
48 public content::NotificationObserver {
49 public:
PDFBrowserTest()50 PDFBrowserTest()
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"));
58 }
59
60 protected:
61 // Use our own TestServer so that we can serve files from the pdf directory.
pdf_test_server()62 net::test_server::EmbeddedTestServer* pdf_test_server() {
63 return &pdf_test_server_;
64 }
65
load_stop_notification_count() const66 int load_stop_notification_count() const {
67 return load_stop_notification_count_;
68 }
69
Load()70 void Load() {
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);
80
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);
85 }
86
VerifySnapshot(const std::string & expected_filename)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();
92 DCHECK(web_contents);
93
94 content::RenderWidgetHost* rwh = web_contents->GetRenderViewHost();
95 rwh->CopyFromBackingStore(
96 gfx::Rect(),
97 gfx::Size(),
98 base::Bind(&PDFBrowserTest::CopyFromBackingStoreCallback, this),
99 SkBitmap::kARGB_8888_Config);
100
101 content::RunMessageLoop();
102
103 if (snapshot_different_) {
104 LOG(INFO) << "Rendering didn't match, see result " <<
105 snapshot_filename_.value();
106 }
107 return !snapshot_different_;
108 }
109
WaitForResponse()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.
120
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));
126 }
127
128 private:
CopyFromBackingStoreCallback(bool success,const SkBitmap & bitmap)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));
140
141 int w, h;
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]);
147
148 int32* pixels = static_cast<int32*>(bitmap.getPixels());
149
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)
162 break;
163 }
164
165 for (snapshot_x_offset = 0; snapshot_x_offset < bitmap.width();
166 ++snapshot_x_offset) {
167 if (pixels[snapshot_x_offset] != bg_color)
168 break;
169 }
170
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;
181 }
182 }
183
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());
190 }
191 }
192 }
193
194 // content::NotificationObserver
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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_++;
200 }
201 }
202
203 // InProcessBrowserTest
SetUpCommandLine(base::CommandLine * command_line)204 virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE {
205 #if defined(OS_LINUX)
206 // Calling RenderWidgetHost::CopyFromBackingStore() with the GPU enabled
207 // fails on Linux.
208 command_line->AppendSwitch(switches::kDisableGpu);
209 #endif
210 }
211
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_;
222
223 net::test_server::EmbeddedTestServer pdf_test_server_;
224 };
225
226
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
231 #else
232 #define MAYBE_Basic DISABLED_Basic
233 #endif
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_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
238 // Win.
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"));
245 #else
246 ASSERT_TRUE(VerifySnapshot("pdf_browsertest.png"));
247 #endif
248 }
249
250 #if defined(GOOGLE_CHROME_BUILD) && (defined(OS_WIN) || defined(OS_LINUX))
251 #define MAYBE_Scroll Scroll
252 #else
253 // TODO(thestig): http://crbug.com/79837, http://crbug.com/332778
254 #define MAYBE_Scroll DISABLED_Scroll
255 #endif
256 // Tests that scrolling works.
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Scroll)257 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_Scroll) {
258 ASSERT_NO_FATAL_FAILURE(Load());
259
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
262 // the moment.
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());
271
272 int y_offset = 0;
273 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
274 browser()->tab_strip_model()->GetActiveWebContents(),
275 "window.domAutomationController.send(plugin.pageYOffset())",
276 &y_offset));
277 ASSERT_GT(y_offset, 0);
278 }
279
280 #if defined(GOOGLE_CHROME_BUILD) && (defined(OS_WIN) || defined(OS_LINUX))
281 #define MAYBE_FindAndCopy FindAndCopy
282 #else
283 // TODO(thestig): http://crbug.com/79837, http://crbug.com/329912
284 #define MAYBE_FindAndCopy DISABLED_FindAndCopy
285 #endif
286 // flaky, disabling on branch
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,DISABLED_FindAndCopy)287 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, DISABLED_FindAndCopy) {
288 ASSERT_NO_FATAL_FAILURE(Load());
289 // Verifies that find in page works.
290 ASSERT_EQ(3, ui_test_utils::FindInPage(
291 browser()->tab_strip_model()->GetActiveWebContents(),
292 base::UTF8ToUTF16("adipiscing"),
293 true, false, NULL, NULL));
294
295 // Verify that copying selected text works.
296 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
297 // Reset the clipboard first.
298 clipboard->Clear(ui::CLIPBOARD_TYPE_COPY_PASTE);
299
300 browser()->tab_strip_model()->GetActiveWebContents()->Copy();
301 ASSERT_NO_FATAL_FAILURE(WaitForResponse());
302
303 std::string text;
304 clipboard->ReadAsciiText(ui::CLIPBOARD_TYPE_COPY_PASTE, &text);
305 ASSERT_EQ("adipiscing", text);
306 }
307
308 const int kLoadingNumberOfParts = 10;
309
310 // Tests that loading async pdfs works correctly (i.e. document fully loads).
311 // This also loads all documents that used to crash, to ensure we don't have
312 // regressions.
313 // If it flakes, reopen http://crbug.com/74548.
314 #if defined(GOOGLE_CHROME_BUILD)
315 #define MAYBE_Loading Loading
316 #else
317 #define MAYBE_Loading DISABLED_Loading
318 #endif
IN_PROC_BROWSER_TEST_P(PDFBrowserTest,MAYBE_Loading)319 IN_PROC_BROWSER_TEST_P(PDFBrowserTest, MAYBE_Loading) {
320 ASSERT_TRUE(pdf_test_server()->InitializeAndWaitUntilReady());
321
322 NavigationController* controller =
323 &(browser()->tab_strip_model()->GetActiveWebContents()->GetController());
324 content::NotificationRegistrar registrar;
325 registrar.Add(this,
326 content::NOTIFICATION_LOAD_STOP,
327 content::Source<NavigationController>(controller));
328 std::string base_url = std::string("/");
329
330 base::FileEnumerator file_enumerator(
331 ui_test_utils::GetTestFilePath(
332 base::FilePath(FILE_PATH_LITERAL("pdf_private")), base::FilePath()),
333 false,
334 base::FileEnumerator::FILES,
335 FILE_PATH_LITERAL("*.pdf"));
336 for (base::FilePath file_path = file_enumerator.Next();
337 !file_path.empty();
338 file_path = file_enumerator.Next()) {
339 std::string filename = file_path.BaseName().MaybeAsASCII();
340 ASSERT_FALSE(filename.empty());
341
342 #if defined(OS_POSIX)
343 if (filename == "sample.pdf")
344 continue; // Crashes on Mac and Linux. http://crbug.com/63549
345 #endif
346
347 // Split the test into smaller sub-tests. Each one only loads
348 // every k-th file.
349 if (static_cast<int>(base::Hash(filename) % kLoadingNumberOfParts) !=
350 GetParam()) {
351 continue;
352 }
353
354 LOG(WARNING) << "PDFBrowserTest.Loading: " << filename;
355
356 GURL url = pdf_test_server()->GetURL(base_url + filename);
357 ui_test_utils::NavigateToURL(browser(), url);
358
359 while (true) {
360 int last_count = load_stop_notification_count();
361 // We might get extraneous chrome::LOAD_STOP notifications when
362 // doing async loading. This happens when the first loader is cancelled
363 // and before creating a byte-range request loader.
364 bool complete = false;
365 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
366 browser()->tab_strip_model()->GetActiveWebContents(),
367 "window.domAutomationController.send(plugin.documentLoadComplete())",
368 &complete));
369 if (complete)
370 break;
371
372 // Check if the LOAD_STOP notification could have come while we run a
373 // nested message loop for the JS call.
374 if (last_count != load_stop_notification_count())
375 continue;
376 content::WaitForLoadStop(
377 browser()->tab_strip_model()->GetActiveWebContents());
378 }
379 }
380 }
381
382 INSTANTIATE_TEST_CASE_P(PDFTestFiles,
383 PDFBrowserTest,
384 testing::Range(0, kLoadingNumberOfParts));
385
386 #if defined(GOOGLE_CHROME_BUILD) && (defined(OS_WIN) || defined(OS_LINUX))
387 #define MAYBE_Action Action
388 #else
389 // http://crbug.com/315160
390 #define MAYBE_Action DISABLED_Action
391 #endif
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Action)392 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_Action) {
393 ASSERT_NO_FATAL_FAILURE(Load());
394
395 ASSERT_TRUE(content::ExecuteScript(
396 browser()->tab_strip_model()->GetActiveWebContents(),
397 "document.getElementsByName('plugin')[0].fitToHeight();"));
398
399 std::string zoom1, zoom2;
400 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
401 browser()->tab_strip_model()->GetActiveWebContents(),
402 "window.domAutomationController.send("
403 " document.getElementsByName('plugin')[0].getZoomLevel().toString())",
404 &zoom1));
405
406 ASSERT_TRUE(content::ExecuteScript(
407 browser()->tab_strip_model()->GetActiveWebContents(),
408 "document.getElementsByName('plugin')[0].fitToWidth();"));
409
410 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
411 browser()->tab_strip_model()->GetActiveWebContents(),
412 "window.domAutomationController.send("
413 " document.getElementsByName('plugin')[0].getZoomLevel().toString())",
414 &zoom2));
415 ASSERT_NE(zoom1, zoom2);
416 }
417
418 #if defined(GOOGLE_CHROME_BUILD) && defined(OS_LINUX)
419 #define MAYBE_OnLoadAndReload OnLoadAndReload
420 #else
421 // Flaky as per http://crbug.com/74549.
422 #define MAYBE_OnLoadAndReload DISABLED_OnLoadAndReload
423 #endif
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_OnLoadAndReload)424 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_OnLoadAndReload) {
425 ASSERT_TRUE(pdf_test_server()->InitializeAndWaitUntilReady());
426
427 GURL url = pdf_test_server()->GetURL("/onload_reload.html");
428 ui_test_utils::NavigateToURL(browser(), url);
429 WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
430
431 content::WindowedNotificationObserver observer(
432 content::NOTIFICATION_LOAD_STOP,
433 content::Source<NavigationController>(
434 &contents->GetController()));
435 ASSERT_TRUE(content::ExecuteScript(
436 browser()->tab_strip_model()->GetActiveWebContents(),
437 "reloadPDF();"));
438 observer.Wait();
439
440 ASSERT_EQ("success", contents->GetURL().query());
441 }
442
443 } // namespace
444