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/file_util.h"
6 #include "base/files/file_enumerator.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"
30 using content::NavigationController;
31 using content::WebContents;
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;
40 class PDFBrowserTest : public InProcessBrowserTest,
41 public testing::WithParamInterface<int>,
42 public content::NotificationObserver {
45 : snapshot_different_(true),
46 next_dummy_search_value_(0),
47 load_stop_notification_count_(0) {
48 pdf_test_server_.ServeFilesFromDirectory(
49 base::FilePath(FILE_PATH_LITERAL("pdf/test")));
53 // Use our own TestServer so that we can serve files from the pdf directory.
54 net::test_server::EmbeddedTestServer* pdf_test_server() {
55 return &pdf_test_server_;
58 int load_stop_notification_count() const {
59 return load_stop_notification_count_;
62 base::FilePath GetPDFTestDir() {
63 return base::FilePath(base::FilePath::kCurrentDirectory).AppendASCII("..").
64 AppendASCII("..").AppendASCII("..").AppendASCII("pdf").
69 // Make sure to set the window size before rendering, as otherwise rendering
70 // to a smaller window and then expanding leads to slight anti-aliasing
71 // differences of the text and the pixel comparison fails.
72 gfx::Rect bounds(gfx::Rect(0, 0, kBrowserWidth, kBrowserHeight));
73 gfx::Rect screen_bounds =
74 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().bounds();
75 ASSERT_GT(screen_bounds.width(), kBrowserWidth);
76 ASSERT_GT(screen_bounds.height(), kBrowserHeight);
77 browser()->window()->SetBounds(bounds);
79 GURL url(ui_test_utils::GetTestUrl(
81 base::FilePath(FILE_PATH_LITERAL("pdf_browsertest.pdf"))));
82 ui_test_utils::NavigateToURL(browser(), url);
85 bool VerifySnapshot(const std::string& expected_filename) {
86 snapshot_different_ = true;
87 expected_filename_ = expected_filename;
88 WebContents* web_contents =
89 browser()->tab_strip_model()->GetActiveWebContents();
92 content::RenderWidgetHost* rwh = web_contents->GetRenderViewHost();
93 rwh->GetSnapshotFromRenderer(gfx::Rect(), base::Bind(
94 &PDFBrowserTest::GetSnapshotFromRendererCallback, this));
96 content::RunMessageLoop();
98 if (snapshot_different_) {
99 LOG(INFO) << "Rendering didn't match, see result " <<
100 snapshot_filename_.value().c_str();
102 return !snapshot_different_;
105 void WaitForResponse() {
106 // Even if the plugin has loaded the data or scrolled, because of how
107 // pepper painting works, we might not have the data. One way to force this
108 // to be flushed is to do a find operation, since on this two-page test
109 // document, it'll wait for us to flush the renderer message loop twice and
110 // also the browser's once, at which point we're guaranteed to have updated
111 // the backingstore. Hacky, but it works.
112 // Note that we need to change the text each time, because if we don't the
113 // renderer code will think the second message is to go to next result, but
114 // there are none so the plugin will assert.
116 string16 query = UTF8ToUTF16(
117 std::string("xyzxyz" + base::IntToString(next_dummy_search_value_++)));
118 ASSERT_EQ(0, ui_test_utils::FindInPage(
119 browser()->tab_strip_model()->GetActiveWebContents(),
120 query, true, false, NULL, NULL));
124 void GetSnapshotFromRendererCallback(bool success,
125 const SkBitmap& bitmap) {
126 base::MessageLoopForUI::current()->Quit();
127 ASSERT_EQ(success, true);
128 base::FilePath reference = ui_test_utils::GetTestFilePath(
130 base::FilePath().AppendASCII(expected_filename_));
131 base::PlatformFileInfo info;
132 ASSERT_TRUE(file_util::GetFileInfo(reference, &info));
133 int size = static_cast<size_t>(info.size);
134 scoped_ptr<char[]> data(new char[size]);
135 ASSERT_EQ(size, file_util::ReadFile(reference, data.get(), size));
138 std::vector<unsigned char> decoded;
139 ASSERT_TRUE(gfx::PNGCodec::Decode(
140 reinterpret_cast<unsigned char*>(data.get()), size,
141 gfx::PNGCodec::FORMAT_BGRA, &decoded, &w, &h));
142 int32* ref_pixels = reinterpret_cast<int32*>(&decoded[0]);
144 int32* pixels = static_cast<int32*>(bitmap.getPixels());
146 // Get the background color, and use it to figure out the x-offsets in
147 // each image. The reason is that depending on the theme in the OS, the
148 // same browser width can lead to slightly different plugin sizes, so the
149 // pdf content will start at different x offsets.
150 // Also note that the images we saved are cut off before the scrollbar, as
151 // that'll change depending on the theme, and also cut off vertically so
152 // that the ui controls don't show up, as those fade-in and so the timing
153 // will affect their transparency.
154 int32 bg_color = ref_pixels[0];
155 int ref_x_offset, snapshot_x_offset;
156 for (ref_x_offset = 0; ref_x_offset < w; ++ref_x_offset) {
157 if (ref_pixels[ref_x_offset] != bg_color)
161 for (snapshot_x_offset = 0; snapshot_x_offset < bitmap.width();
162 ++snapshot_x_offset) {
163 if (pixels[snapshot_x_offset] != bg_color)
167 int x_max = std::min(
168 w - ref_x_offset, bitmap.width() - snapshot_x_offset);
169 int y_max = std::min(h, bitmap.height());
170 int stride = bitmap.rowBytes();
171 snapshot_different_ = false;
172 for (int y = 0; y < y_max && !snapshot_different_; ++y) {
173 for (int x = 0; x < x_max && !snapshot_different_; ++x) {
174 if (pixels[y * stride / sizeof(int32) + x + snapshot_x_offset] !=
175 ref_pixels[y * w + x + ref_x_offset])
176 snapshot_different_ = true;
180 if (snapshot_different_) {
181 std::vector<unsigned char> png_data;
182 gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data);
183 if (file_util::CreateTemporaryFile(&snapshot_filename_)) {
184 file_util::WriteFile(snapshot_filename_,
185 reinterpret_cast<char*>(&png_data[0]), png_data.size());
190 // content::NotificationObserver
191 virtual void Observe(int type,
192 const content::NotificationSource& source,
193 const content::NotificationDetails& details) {
194 if (type == content::NOTIFICATION_LOAD_STOP) {
195 load_stop_notification_count_++;
199 // True if the snapshot differed from the expected value.
200 bool snapshot_different_;
201 // Internal variable used to synchronize to the renderer.
202 int next_dummy_search_value_;
203 // The filename of the bitmap to compare the snapshot to.
204 std::string expected_filename_;
205 // If the snapshot is different, holds the location where it's saved.
206 base::FilePath snapshot_filename_;
207 // How many times we've seen chrome::LOAD_STOP.
208 int load_stop_notification_count_;
210 net::test_server::EmbeddedTestServer pdf_test_server_;
213 #if defined(OS_CHROMEOS)
214 // TODO(sanjeevr): http://crbug.com/79837
215 #define MAYBE_Basic DISABLED_Basic
217 #define MAYBE_Basic Basic
219 // Tests basic PDF rendering. This can be broken depending on bad merges with
220 // the vendor, so it's important that we have basic sanity checking.
221 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_Basic) {
222 ASSERT_NO_FATAL_FAILURE(Load());
223 ASSERT_NO_FATAL_FAILURE(WaitForResponse());
224 // OS X uses CoreText, and FreeType renders slightly different on Linux and
226 #if defined(OS_MACOSX)
227 // The bots render differently than locally, see http://crbug.com/142531.
228 ASSERT_TRUE(VerifySnapshot("pdf_browsertest_mac.png") ||
229 VerifySnapshot("pdf_browsertest_mac2.png"));
230 #elif defined(OS_LINUX)
231 ASSERT_TRUE(VerifySnapshot("pdf_browsertest_linux.png"));
233 ASSERT_TRUE(VerifySnapshot("pdf_browsertest.png"));
237 #if defined(OS_CHROMEOS)
238 // TODO(sanjeevr): http://crbug.com/79837
239 #define MAYBE_Scroll DISABLED_Scroll
241 #define MAYBE_Scroll Scroll
243 // Tests that scrolling works.
244 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_Scroll) {
245 ASSERT_NO_FATAL_FAILURE(Load());
247 // We use wheel mouse event since that's the only one we can easily push to
248 // the renderer. There's no way to push a cross-platform keyboard event at
250 WebKit::WebMouseWheelEvent wheel_event;
251 wheel_event.type = WebKit::WebInputEvent::MouseWheel;
252 wheel_event.deltaY = -200;
253 wheel_event.wheelTicksY = -2;
254 WebContents* web_contents =
255 browser()->tab_strip_model()->GetActiveWebContents();
256 web_contents->GetRenderViewHost()->ForwardWheelEvent(wheel_event);
257 ASSERT_NO_FATAL_FAILURE(WaitForResponse());
260 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
261 browser()->tab_strip_model()->GetActiveWebContents(),
262 "window.domAutomationController.send(plugin.pageYOffset())",
264 ASSERT_GT(y_offset, 0);
267 #if defined(OS_CHROMEOS)
268 // TODO(sanjeevr): http://crbug.com/79837
269 #define MAYBE_FindAndCopy DISABLED_FindAndCopy
271 #define MAYBE_FindAndCopy FindAndCopy
273 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_FindAndCopy) {
274 ASSERT_NO_FATAL_FAILURE(Load());
275 // Verifies that find in page works.
276 ASSERT_EQ(3, ui_test_utils::FindInPage(
277 browser()->tab_strip_model()->GetActiveWebContents(),
278 UTF8ToUTF16("adipiscing"),
279 true, false, NULL, NULL));
281 // Verify that copying selected text works.
282 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
283 // Reset the clipboard first.
284 ui::Clipboard::ObjectMap objects;
285 ui::Clipboard::ObjectMapParams params;
286 params.push_back(std::vector<char>());
287 objects[ui::Clipboard::CBF_TEXT] = params;
288 clipboard->WriteObjects(ui::CLIPBOARD_TYPE_COPY_PASTE, objects);
290 browser()->tab_strip_model()->GetActiveWebContents()->
291 GetRenderViewHost()->Copy();
292 ASSERT_NO_FATAL_FAILURE(WaitForResponse());
295 clipboard->ReadAsciiText(ui::CLIPBOARD_TYPE_COPY_PASTE, &text);
296 ASSERT_EQ("adipiscing", text);
299 const int kLoadingNumberOfParts = 10;
301 // Tests that loading async pdfs works correctly (i.e. document fully loads).
302 // This also loads all documents that used to crash, to ensure we don't have
304 // If it flakes, reopen http://crbug.com/74548.
305 IN_PROC_BROWSER_TEST_P(PDFBrowserTest, Loading) {
306 ASSERT_TRUE(pdf_test_server()->InitializeAndWaitUntilReady());
308 NavigationController* controller =
309 &(browser()->tab_strip_model()->GetActiveWebContents()->GetController());
310 content::NotificationRegistrar registrar;
312 content::NOTIFICATION_LOAD_STOP,
313 content::Source<NavigationController>(controller));
314 std::string base_url = std::string("/");
316 base::FileEnumerator file_enumerator(
317 ui_test_utils::GetTestFilePath(GetPDFTestDir(), base::FilePath()),
319 base::FileEnumerator::FILES,
320 FILE_PATH_LITERAL("*.pdf"));
321 for (base::FilePath file_path = file_enumerator.Next();
323 file_path = file_enumerator.Next()) {
324 std::string filename = file_path.BaseName().MaybeAsASCII();
325 ASSERT_FALSE(filename.empty());
327 #if defined(OS_POSIX)
328 if (filename == "sample.pdf")
329 continue; // Crashes on Mac and Linux. http://crbug.com/63549
332 // Split the test into smaller sub-tests. Each one only loads
334 if (static_cast<int>(base::Hash(filename) % kLoadingNumberOfParts) !=
339 LOG(WARNING) << "PDFBrowserTest.Loading: " << filename;
341 GURL url = pdf_test_server()->GetURL(base_url + filename);
342 ui_test_utils::NavigateToURL(browser(), url);
345 int last_count = load_stop_notification_count();
346 // We might get extraneous chrome::LOAD_STOP notifications when
347 // doing async loading. This happens when the first loader is cancelled
348 // and before creating a byte-range request loader.
349 bool complete = false;
350 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
351 browser()->tab_strip_model()->GetActiveWebContents(),
352 "window.domAutomationController.send(plugin.documentLoadComplete())",
357 // Check if the LOAD_STOP notification could have come while we run a
358 // nested message loop for the JS call.
359 if (last_count != load_stop_notification_count())
361 content::WaitForLoadStop(
362 browser()->tab_strip_model()->GetActiveWebContents());
367 INSTANTIATE_TEST_CASE_P(PDFTestFiles,
369 testing::Range(0, kLoadingNumberOfParts));
371 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, Action) {
372 ASSERT_NO_FATAL_FAILURE(Load());
374 ASSERT_TRUE(content::ExecuteScript(
375 browser()->tab_strip_model()->GetActiveWebContents(),
376 "document.getElementsByName('plugin')[0].fitToHeight();"));
378 std::string zoom1, zoom2;
379 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
380 browser()->tab_strip_model()->GetActiveWebContents(),
381 "window.domAutomationController.send("
382 " document.getElementsByName('plugin')[0].getZoomLevel().toString())",
385 ASSERT_TRUE(content::ExecuteScript(
386 browser()->tab_strip_model()->GetActiveWebContents(),
387 "document.getElementsByName('plugin')[0].fitToWidth();"));
389 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
390 browser()->tab_strip_model()->GetActiveWebContents(),
391 "window.domAutomationController.send("
392 " document.getElementsByName('plugin')[0].getZoomLevel().toString())",
394 ASSERT_NE(zoom1, zoom2);
397 // Flaky as per http://crbug.com/74549.
398 IN_PROC_BROWSER_TEST_F(PDFBrowserTest, DISABLED_OnLoadAndReload) {
399 ASSERT_TRUE(pdf_test_server()->InitializeAndWaitUntilReady());
401 GURL url = pdf_test_server()->GetURL("/onload_reload.html");
402 ui_test_utils::NavigateToURL(browser(), url);
404 content::WindowedNotificationObserver observer(
405 content::NOTIFICATION_LOAD_STOP,
406 content::Source<NavigationController>(
407 &browser()->tab_strip_model()->GetActiveWebContents()->
409 ASSERT_TRUE(content::ExecuteScript(
410 browser()->tab_strip_model()->GetActiveWebContents(),
415 browser()->tab_strip_model()->GetActiveWebContents()->