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