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