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: 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. 62 net::test_server::EmbeddedTestServer* pdf_test_server() { 63 return &pdf_test_server_; 64 } 65 66 int load_stop_notification_count() const { 67 return load_stop_notification_count_; 68 } 69 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 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 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: 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 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 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 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. 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 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 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 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 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