pdf_browsertest.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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/ui/browser.h" 13#include "chrome/browser/ui/browser_window.h" 14#include "chrome/browser/ui/tabs/tab_strip_model.h" 15#include "chrome/common/chrome_notification_types.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/spawned_test_server/spawned_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 30using content::NavigationController; 31using content::WebContents; 32 33namespace { 34 35// Include things like browser frame and scrollbar and make sure we're bigger 36// than the test pdf document. 37static const int kBrowserWidth = 1000; 38static const int kBrowserHeight = 600; 39 40class 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_.reset(new net::SpawnedTestServer( 49 net::SpawnedTestServer::TYPE_HTTP, 50 net::SpawnedTestServer::kLocalhost, 51 base::FilePath(FILE_PATH_LITERAL("pdf/test")))); 52 } 53 54 protected: 55 // Use our own TestServer so that we can serve files from the pdf directory. 56 net::SpawnedTestServer* pdf_test_server() { return pdf_test_server_.get(); } 57 58 int load_stop_notification_count() const { 59 return load_stop_notification_count_; 60 } 61 62 base::FilePath GetPDFTestDir() { 63 return base::FilePath(base::FilePath::kCurrentDirectory).AppendASCII(".."). 64 AppendASCII("..").AppendASCII("..").AppendASCII("pdf"). 65 AppendASCII("test"); 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 GetPDFTestDir(), 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->GetSnapshotFromRenderer(gfx::Rect(), base::Bind( 94 &PDFBrowserTest::GetSnapshotFromRendererCallback, this)); 95 96 content::RunMessageLoop(); 97 98 if (snapshot_different_) { 99 LOG(INFO) << "Rendering didn't match, see result " << 100 snapshot_filename_.value().c_str(); 101 } 102 return !snapshot_different_; 103 } 104 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. 115 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)); 121 } 122 123 private: 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( 129 GetPDFTestDir(), 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)); 136 137 int w, h; 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]); 143 144 int32* pixels = static_cast<int32*>(bitmap.getPixels()); 145 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) 158 break; 159 } 160 161 for (snapshot_x_offset = 0; snapshot_x_offset < bitmap.width(); 162 ++snapshot_x_offset) { 163 if (pixels[snapshot_x_offset] != bg_color) 164 break; 165 } 166 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; 177 } 178 } 179 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()); 186 } 187 } 188 } 189 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_++; 196 } 197 } 198 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_; 209 210 scoped_ptr<net::SpawnedTestServer> pdf_test_server_; 211}; 212 213#if defined(OS_CHROMEOS) 214// TODO(sanjeevr): http://crbug.com/79837 215#define MAYBE_Basic DISABLED_Basic 216#else 217#define MAYBE_Basic Basic 218#endif 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. 221IN_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 225 // Win. 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")); 232#else 233 ASSERT_TRUE(VerifySnapshot("pdf_browsertest.png")); 234#endif 235} 236 237#if defined(OS_CHROMEOS) 238// TODO(sanjeevr): http://crbug.com/79837 239#define MAYBE_Scroll DISABLED_Scroll 240#else 241#define MAYBE_Scroll Scroll 242#endif 243// Tests that scrolling works. 244IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_Scroll) { 245 ASSERT_NO_FATAL_FAILURE(Load()); 246 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 249 // the moment. 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()); 258 259 int y_offset = 0; 260 ASSERT_TRUE(content::ExecuteScriptAndExtractInt( 261 browser()->tab_strip_model()->GetActiveWebContents(), 262 "window.domAutomationController.send(plugin.pageYOffset())", 263 &y_offset)); 264 ASSERT_GT(y_offset, 0); 265} 266 267#if defined(OS_CHROMEOS) 268// TODO(sanjeevr): http://crbug.com/79837 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 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 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::BUFFER_STANDARD, objects); 289 290 browser()->tab_strip_model()->GetActiveWebContents()-> 291 GetRenderViewHost()->Copy(); 292 ASSERT_NO_FATAL_FAILURE(WaitForResponse()); 293 294 std::string text; 295 clipboard->ReadAsciiText(ui::Clipboard::BUFFER_STANDARD, &text); 296 ASSERT_EQ("adipiscing", text); 297} 298 299const int kLoadingNumberOfParts = 10; 300 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 303// regressions. 304// If it flakes, reopen http://crbug.com/74548. 305IN_PROC_BROWSER_TEST_P(PDFBrowserTest, Loading) { 306 ASSERT_TRUE(pdf_test_server()->Start()); 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("files/"); 315 316 base::FileEnumerator file_enumerator( 317 ui_test_utils::GetTestFilePath(GetPDFTestDir(), base::FilePath()), 318 false, 319 base::FileEnumerator::FILES, 320 FILE_PATH_LITERAL("*.pdf")); 321 for (base::FilePath file_path = file_enumerator.Next(); 322 !file_path.empty(); 323 file_path = file_enumerator.Next()) { 324 std::string filename = file_path.BaseName().MaybeAsASCII(); 325 ASSERT_FALSE(filename.empty()); 326 327#if defined(OS_POSIX) 328 if (filename == "sample.pdf") 329 continue; // Crashes on Mac and Linux. http://crbug.com/63549 330#endif 331 332 // Split the test into smaller sub-tests. Each one only loads 333 // every k-th file. 334 if (static_cast<int>(base::Hash(filename) % kLoadingNumberOfParts) != 335 GetParam()) { 336 continue; 337 } 338 339 LOG(WARNING) << "PDFBrowserTest.Loading: " << filename; 340 341 GURL url = pdf_test_server()->GetURL(base_url + filename); 342 ui_test_utils::NavigateToURL(browser(), url); 343 344 while (true) { 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())", 353 &complete)); 354 if (complete) 355 break; 356 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()) 360 continue; 361 content::WaitForLoadStop( 362 browser()->tab_strip_model()->GetActiveWebContents()); 363 } 364 } 365} 366 367INSTANTIATE_TEST_CASE_P(PDFTestFiles, 368 PDFBrowserTest, 369 testing::Range(0, kLoadingNumberOfParts)); 370 371IN_PROC_BROWSER_TEST_F(PDFBrowserTest, Action) { 372 ASSERT_NO_FATAL_FAILURE(Load()); 373 374 ASSERT_TRUE(content::ExecuteScript( 375 browser()->tab_strip_model()->GetActiveWebContents(), 376 "document.getElementsByName('plugin')[0].fitToHeight();")); 377 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())", 383 &zoom1)); 384 385 ASSERT_TRUE(content::ExecuteScript( 386 browser()->tab_strip_model()->GetActiveWebContents(), 387 "document.getElementsByName('plugin')[0].fitToWidth();")); 388 389 ASSERT_TRUE(content::ExecuteScriptAndExtractString( 390 browser()->tab_strip_model()->GetActiveWebContents(), 391 "window.domAutomationController.send(" 392 " document.getElementsByName('plugin')[0].getZoomLevel().toString())", 393 &zoom2)); 394 ASSERT_NE(zoom1, zoom2); 395} 396 397// Flaky as per http://crbug.com/74549. 398IN_PROC_BROWSER_TEST_F(PDFBrowserTest, DISABLED_OnLoadAndReload) { 399 ASSERT_TRUE(pdf_test_server()->Start()); 400 401 GURL url = pdf_test_server()->GetURL("files/onload_reload.html"); 402 ui_test_utils::NavigateToURL(browser(), url); 403 404 content::WindowedNotificationObserver observer( 405 content::NOTIFICATION_LOAD_STOP, 406 content::Source<NavigationController>( 407 &browser()->tab_strip_model()->GetActiveWebContents()-> 408 GetController())); 409 ASSERT_TRUE(content::ExecuteScript( 410 browser()->tab_strip_model()->GetActiveWebContents(), 411 "reloadPDF();")); 412 observer.Wait(); 413 414 ASSERT_EQ("success", 415 browser()->tab_strip_model()->GetActiveWebContents()-> 416 GetURL().query()); 417} 418 419} // namespace 420