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