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