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/file_util.h"
6 #include "base/files/file_enumerator.h"
7 #include "base/hash.h"
8 #include "base/path_service.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #include "chrome/browser/ui/tabs/tab_strip_model.h"
16 #include "chrome/common/chrome_paths.h"
17 #include "chrome/test/base/in_process_browser_test.h"
18 #include "chrome/test/base/ui_test_utils.h"
19 #include "content/public/browser/navigation_controller.h"
20 #include "content/public/browser/notification_observer.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/test/browser_test_utils.h"
24 #include "net/test/embedded_test_server/embedded_test_server.h"
25 #include "third_party/skia/include/core/SkBitmap.h"
26 #include "ui/base/clipboard/clipboard.h"
27 #include "ui/gfx/codec/png_codec.h"
28 #include "ui/gfx/screen.h"
29
30 using content::NavigationController;
31 using content::WebContents;
32
33 namespace {
34
35 // Include things like browser frame and scrollbar and make sure we're bigger
36 // than the test pdf document.
37 static const int kBrowserWidth = 1000;
38 static const int kBrowserHeight = 600;
39
40 class PDFBrowserTest : public InProcessBrowserTest,
41 public testing::WithParamInterface<int>,
42 public content::NotificationObserver {
43 public:
PDFBrowserTest()44 PDFBrowserTest()
45 : snapshot_different_(true),
46 next_dummy_search_value_(0),
47 load_stop_notification_count_(0) {
48 base::FilePath src_dir;
49 EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
50 pdf_test_server_.ServeFilesFromDirectory(src_dir.AppendASCII("pdf/test"));
51 }
52
53 protected:
54 // Use our own TestServer so that we can serve files from the pdf directory.
pdf_test_server()55 net::test_server::EmbeddedTestServer* pdf_test_server() {
56 return &pdf_test_server_;
57 }
58
load_stop_notification_count() const59 int load_stop_notification_count() const {
60 return load_stop_notification_count_;
61 }
62
GetPDFTestDir()63 base::FilePath GetPDFTestDir() {
64 return base::FilePath(base::FilePath::kCurrentDirectory).AppendASCII("..").
65 AppendASCII("..").AppendASCII("..").AppendASCII("pdf").
66 AppendASCII("test");
67 }
68
Load()69 void Load() {
70 // Make sure to set the window size before rendering, as otherwise rendering
71 // to a smaller window and then expanding leads to slight anti-aliasing
72 // differences of the text and the pixel comparison fails.
73 gfx::Rect bounds(gfx::Rect(0, 0, kBrowserWidth, kBrowserHeight));
74 gfx::Rect screen_bounds =
75 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().bounds();
76 ASSERT_GT(screen_bounds.width(), kBrowserWidth);
77 ASSERT_GT(screen_bounds.height(), kBrowserHeight);
78 browser()->window()->SetBounds(bounds);
79
80 GURL url(ui_test_utils::GetTestUrl(
81 GetPDFTestDir(),
82 base::FilePath(FILE_PATH_LITERAL("pdf_browsertest.pdf"))));
83 ui_test_utils::NavigateToURL(browser(), url);
84 }
85
VerifySnapshot(const std::string & expected_filename)86 bool VerifySnapshot(const std::string& expected_filename) {
87 snapshot_different_ = true;
88 expected_filename_ = expected_filename;
89 WebContents* web_contents =
90 browser()->tab_strip_model()->GetActiveWebContents();
91 DCHECK(web_contents);
92
93 content::RenderWidgetHost* rwh = web_contents->GetRenderViewHost();
94 rwh->GetSnapshotFromRenderer(gfx::Rect(), base::Bind(
95 &PDFBrowserTest::GetSnapshotFromRendererCallback, this));
96
97 content::RunMessageLoop();
98
99 if (snapshot_different_) {
100 LOG(INFO) << "Rendering didn't match, see result " <<
101 snapshot_filename_.value().c_str();
102 }
103 return !snapshot_different_;
104 }
105
WaitForResponse()106 void WaitForResponse() {
107 // Even if the plugin has loaded the data or scrolled, because of how
108 // pepper painting works, we might not have the data. One way to force this
109 // to be flushed is to do a find operation, since on this two-page test
110 // document, it'll wait for us to flush the renderer message loop twice and
111 // also the browser's once, at which point we're guaranteed to have updated
112 // the backingstore. Hacky, but it works.
113 // Note that we need to change the text each time, because if we don't the
114 // renderer code will think the second message is to go to next result, but
115 // there are none so the plugin will assert.
116
117 base::string16 query = UTF8ToUTF16(
118 std::string("xyzxyz" + base::IntToString(next_dummy_search_value_++)));
119 ASSERT_EQ(0, ui_test_utils::FindInPage(
120 browser()->tab_strip_model()->GetActiveWebContents(),
121 query, true, false, NULL, NULL));
122 }
123
124 private:
GetSnapshotFromRendererCallback(bool success,const SkBitmap & bitmap)125 void GetSnapshotFromRendererCallback(bool success,
126 const SkBitmap& bitmap) {
127 base::MessageLoopForUI::current()->Quit();
128 ASSERT_EQ(success, true);
129 base::FilePath reference = ui_test_utils::GetTestFilePath(
130 GetPDFTestDir(),
131 base::FilePath().AppendASCII(expected_filename_));
132 base::PlatformFileInfo info;
133 ASSERT_TRUE(base::GetFileInfo(reference, &info));
134 int size = static_cast<size_t>(info.size);
135 scoped_ptr<char[]> data(new char[size]);
136 ASSERT_EQ(size, base::ReadFile(reference, data.get(), size));
137
138 int w, h;
139 std::vector<unsigned char> decoded;
140 ASSERT_TRUE(gfx::PNGCodec::Decode(
141 reinterpret_cast<unsigned char*>(data.get()), size,
142 gfx::PNGCodec::FORMAT_BGRA, &decoded, &w, &h));
143 int32* ref_pixels = reinterpret_cast<int32*>(&decoded[0]);
144
145 int32* pixels = static_cast<int32*>(bitmap.getPixels());
146
147 // Get the background color, and use it to figure out the x-offsets in
148 // each image. The reason is that depending on the theme in the OS, the
149 // same browser width can lead to slightly different plugin sizes, so the
150 // pdf content will start at different x offsets.
151 // Also note that the images we saved are cut off before the scrollbar, as
152 // that'll change depending on the theme, and also cut off vertically so
153 // that the ui controls don't show up, as those fade-in and so the timing
154 // will affect their transparency.
155 int32 bg_color = ref_pixels[0];
156 int ref_x_offset, snapshot_x_offset;
157 for (ref_x_offset = 0; ref_x_offset < w; ++ref_x_offset) {
158 if (ref_pixels[ref_x_offset] != bg_color)
159 break;
160 }
161
162 for (snapshot_x_offset = 0; snapshot_x_offset < bitmap.width();
163 ++snapshot_x_offset) {
164 if (pixels[snapshot_x_offset] != bg_color)
165 break;
166 }
167
168 int x_max = std::min(
169 w - ref_x_offset, bitmap.width() - snapshot_x_offset);
170 int y_max = std::min(h, bitmap.height());
171 int stride = bitmap.rowBytes();
172 snapshot_different_ = false;
173 for (int y = 0; y < y_max && !snapshot_different_; ++y) {
174 for (int x = 0; x < x_max && !snapshot_different_; ++x) {
175 if (pixels[y * stride / sizeof(int32) + x + snapshot_x_offset] !=
176 ref_pixels[y * w + x + ref_x_offset])
177 snapshot_different_ = true;
178 }
179 }
180
181 if (snapshot_different_) {
182 std::vector<unsigned char> png_data;
183 gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data);
184 if (base::CreateTemporaryFile(&snapshot_filename_)) {
185 file_util::WriteFile(snapshot_filename_,
186 reinterpret_cast<char*>(&png_data[0]), png_data.size());
187 }
188 }
189 }
190
191 // content::NotificationObserver
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)192 virtual void Observe(int type,
193 const content::NotificationSource& source,
194 const content::NotificationDetails& details) {
195 if (type == content::NOTIFICATION_LOAD_STOP) {
196 load_stop_notification_count_++;
197 }
198 }
199
200 // True if the snapshot differed from the expected value.
201 bool snapshot_different_;
202 // Internal variable used to synchronize to the renderer.
203 int next_dummy_search_value_;
204 // The filename of the bitmap to compare the snapshot to.
205 std::string expected_filename_;
206 // If the snapshot is different, holds the location where it's saved.
207 base::FilePath snapshot_filename_;
208 // How many times we've seen chrome::LOAD_STOP.
209 int load_stop_notification_count_;
210
211 net::test_server::EmbeddedTestServer pdf_test_server_;
212 };
213
214 #if defined(OS_CHROMEOS)
215 // TODO(sanjeevr): http://crbug.com/79837
216 #define MAYBE_Basic DISABLED_Basic
217 #else
218 #define MAYBE_Basic Basic
219 #endif
220 // Tests basic PDF rendering. This can be broken depending on bad merges with
221 // the vendor, so it's important that we have basic sanity checking.
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Basic)222 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_Basic) {
223 ASSERT_NO_FATAL_FAILURE(Load());
224 ASSERT_NO_FATAL_FAILURE(WaitForResponse());
225 // OS X uses CoreText, and FreeType renders slightly different on Linux and
226 // Win.
227 #if defined(OS_MACOSX)
228 // The bots render differently than locally, see http://crbug.com/142531.
229 ASSERT_TRUE(VerifySnapshot("pdf_browsertest_mac.png") ||
230 VerifySnapshot("pdf_browsertest_mac2.png"));
231 #elif defined(OS_LINUX)
232 ASSERT_TRUE(VerifySnapshot("pdf_browsertest_linux.png"));
233 #else
234 ASSERT_TRUE(VerifySnapshot("pdf_browsertest.png"));
235 #endif
236 }
237
238 #if defined(OS_CHROMEOS)
239 // TODO(sanjeevr): http://crbug.com/79837
240 #define MAYBE_Scroll DISABLED_Scroll
241 #else
242 #define MAYBE_Scroll Scroll
243 #endif
244 // Tests that scrolling works.
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Scroll)245 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_Scroll) {
246 ASSERT_NO_FATAL_FAILURE(Load());
247
248 // We use wheel mouse event since that's the only one we can easily push to
249 // the renderer. There's no way to push a cross-platform keyboard event at
250 // the moment.
251 blink::WebMouseWheelEvent wheel_event;
252 wheel_event.type = blink::WebInputEvent::MouseWheel;
253 wheel_event.deltaY = -200;
254 wheel_event.wheelTicksY = -2;
255 WebContents* web_contents =
256 browser()->tab_strip_model()->GetActiveWebContents();
257 web_contents->GetRenderViewHost()->ForwardWheelEvent(wheel_event);
258 ASSERT_NO_FATAL_FAILURE(WaitForResponse());
259
260 int y_offset = 0;
261 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
262 browser()->tab_strip_model()->GetActiveWebContents(),
263 "window.domAutomationController.send(plugin.pageYOffset())",
264 &y_offset));
265 ASSERT_GT(y_offset, 0);
266 }
267
268 #if defined(OS_CHROMEOS)
269 // TODO(sanjeevr): http://crbug.com/79837
270 #define MAYBE_FindAndCopy DISABLED_FindAndCopy
271 #else
272 #define MAYBE_FindAndCopy FindAndCopy
273 #endif
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_FindAndCopy)274 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_FindAndCopy) {
275 ASSERT_NO_FATAL_FAILURE(Load());
276 // Verifies that find in page works.
277 ASSERT_EQ(3, ui_test_utils::FindInPage(
278 browser()->tab_strip_model()->GetActiveWebContents(),
279 UTF8ToUTF16("adipiscing"),
280 true, false, NULL, NULL));
281
282 // Verify that copying selected text works.
283 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
284 // Reset the clipboard first.
285 ui::Clipboard::ObjectMap objects;
286 ui::Clipboard::ObjectMapParams params;
287 params.push_back(std::vector<char>());
288 objects[ui::Clipboard::CBF_TEXT] = params;
289 clipboard->WriteObjects(ui::CLIPBOARD_TYPE_COPY_PASTE, objects);
290
291 browser()->tab_strip_model()->GetActiveWebContents()->
292 GetRenderViewHost()->Copy();
293 ASSERT_NO_FATAL_FAILURE(WaitForResponse());
294
295 std::string text;
296 clipboard->ReadAsciiText(ui::CLIPBOARD_TYPE_COPY_PASTE, &text);
297 ASSERT_EQ("adipiscing", text);
298 }
299
300 const int kLoadingNumberOfParts = 10;
301
302 // Tests that loading async pdfs works correctly (i.e. document fully loads).
303 // This also loads all documents that used to crash, to ensure we don't have
304 // regressions.
305 // If it flakes, reopen http://crbug.com/74548.
IN_PROC_BROWSER_TEST_P(PDFBrowserTest,Loading)306 IN_PROC_BROWSER_TEST_P(PDFBrowserTest, Loading) {
307 ASSERT_TRUE(pdf_test_server()->InitializeAndWaitUntilReady());
308
309 NavigationController* controller =
310 &(browser()->tab_strip_model()->GetActiveWebContents()->GetController());
311 content::NotificationRegistrar registrar;
312 registrar.Add(this,
313 content::NOTIFICATION_LOAD_STOP,
314 content::Source<NavigationController>(controller));
315 std::string base_url = std::string("/");
316
317 base::FileEnumerator file_enumerator(
318 ui_test_utils::GetTestFilePath(GetPDFTestDir(), base::FilePath()),
319 false,
320 base::FileEnumerator::FILES,
321 FILE_PATH_LITERAL("*.pdf"));
322 for (base::FilePath file_path = file_enumerator.Next();
323 !file_path.empty();
324 file_path = file_enumerator.Next()) {
325 std::string filename = file_path.BaseName().MaybeAsASCII();
326 ASSERT_FALSE(filename.empty());
327
328 #if defined(OS_POSIX)
329 if (filename == "sample.pdf")
330 continue; // Crashes on Mac and Linux. http://crbug.com/63549
331 #endif
332
333 // Split the test into smaller sub-tests. Each one only loads
334 // every k-th file.
335 if (static_cast<int>(base::Hash(filename) % kLoadingNumberOfParts) !=
336 GetParam()) {
337 continue;
338 }
339
340 LOG(WARNING) << "PDFBrowserTest.Loading: " << filename;
341
342 GURL url = pdf_test_server()->GetURL(base_url + filename);
343 ui_test_utils::NavigateToURL(browser(), url);
344
345 while (true) {
346 int last_count = load_stop_notification_count();
347 // We might get extraneous chrome::LOAD_STOP notifications when
348 // doing async loading. This happens when the first loader is cancelled
349 // and before creating a byte-range request loader.
350 bool complete = false;
351 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
352 browser()->tab_strip_model()->GetActiveWebContents(),
353 "window.domAutomationController.send(plugin.documentLoadComplete())",
354 &complete));
355 if (complete)
356 break;
357
358 // Check if the LOAD_STOP notification could have come while we run a
359 // nested message loop for the JS call.
360 if (last_count != load_stop_notification_count())
361 continue;
362 content::WaitForLoadStop(
363 browser()->tab_strip_model()->GetActiveWebContents());
364 }
365 }
366 }
367
368 INSTANTIATE_TEST_CASE_P(PDFTestFiles,
369 PDFBrowserTest,
370 testing::Range(0, kLoadingNumberOfParts));
371
372 #if defined(GOOGLE_CHROME_BUILD) && defined(OS_MACOSX)
373 // http://crbug.com/315160
374 #define MAYBE_Action DISABLED_Action
375 #else
376 #define MAYBE_Action Action
377 #endif
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Action)378 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_Action) {
379 ASSERT_NO_FATAL_FAILURE(Load());
380
381 ASSERT_TRUE(content::ExecuteScript(
382 browser()->tab_strip_model()->GetActiveWebContents(),
383 "document.getElementsByName('plugin')[0].fitToHeight();"));
384
385 std::string zoom1, zoom2;
386 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
387 browser()->tab_strip_model()->GetActiveWebContents(),
388 "window.domAutomationController.send("
389 " document.getElementsByName('plugin')[0].getZoomLevel().toString())",
390 &zoom1));
391
392 ASSERT_TRUE(content::ExecuteScript(
393 browser()->tab_strip_model()->GetActiveWebContents(),
394 "document.getElementsByName('plugin')[0].fitToWidth();"));
395
396 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
397 browser()->tab_strip_model()->GetActiveWebContents(),
398 "window.domAutomationController.send("
399 " document.getElementsByName('plugin')[0].getZoomLevel().toString())",
400 &zoom2));
401 ASSERT_NE(zoom1, zoom2);
402 }
403
404 // Flaky as per http://crbug.com/74549.
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,DISABLED_OnLoadAndReload)405 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, DISABLED_OnLoadAndReload) {
406 ASSERT_TRUE(pdf_test_server()->InitializeAndWaitUntilReady());
407
408 GURL url = pdf_test_server()->GetURL("/onload_reload.html");
409 ui_test_utils::NavigateToURL(browser(), url);
410
411 content::WindowedNotificationObserver observer(
412 content::NOTIFICATION_LOAD_STOP,
413 content::Source<NavigationController>(
414 &browser()->tab_strip_model()->GetActiveWebContents()->
415 GetController()));
416 ASSERT_TRUE(content::ExecuteScript(
417 browser()->tab_strip_model()->GetActiveWebContents(),
418 "reloadPDF();"));
419 observer.Wait();
420
421 ASSERT_EQ("success",
422 browser()->tab_strip_model()->GetActiveWebContents()->
423 GetURL().query());
424 }
425
426 } // namespace
427