pdf_browsertest.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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.h"
7#include "base/files/file_enumerator.h"
8#include "base/hash.h"
9#include "base/path_service.h"
10#include "base/strings/string_number_conversions.h"
11#include "base/strings/string_util.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/browser/chrome_notification_types.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/browser_window.h"
16#include "chrome/browser/ui/tabs/tab_strip_model.h"
17#include "chrome/common/chrome_paths.h"
18#include "chrome/test/base/in_process_browser_test.h"
19#include "chrome/test/base/ui_test_utils.h"
20#include "content/public/browser/navigation_controller.h"
21#include "content/public/browser/notification_observer.h"
22#include "content/public/browser/render_view_host.h"
23#include "content/public/browser/web_contents.h"
24#include "content/public/test/browser_test_utils.h"
25#include "net/test/embedded_test_server/embedded_test_server.h"
26#include "third_party/skia/include/core/SkBitmap.h"
27#include "ui/base/clipboard/clipboard.h"
28#include "ui/gfx/codec/png_codec.h"
29#include "ui/gfx/screen.h"
30
31using content::NavigationController;
32using content::WebContents;
33
34// Note: All tests in here require the internal PDF plugin, so they're disabled
35// in non-official builds. We still compile them though, to prevent bitrot.
36
37namespace {
38
39// Include things like browser frame and scrollbar and make sure we're bigger
40// than the test pdf document.
41static const int kBrowserWidth = 1000;
42static const int kBrowserHeight = 600;
43
44class PDFBrowserTest : public InProcessBrowserTest,
45                       public testing::WithParamInterface<int>,
46                       public content::NotificationObserver {
47 public:
48  PDFBrowserTest()
49      : snapshot_different_(true),
50        next_dummy_search_value_(0),
51        load_stop_notification_count_(0) {
52    base::FilePath src_dir;
53    EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
54    pdf_test_server_.ServeFilesFromDirectory(src_dir.AppendASCII(
55        "chrome/test/data/pdf_private"));
56  }
57
58 protected:
59  // Use our own TestServer so that we can serve files from the pdf directory.
60  net::test_server::EmbeddedTestServer* pdf_test_server() {
61    return &pdf_test_server_;
62  }
63
64  int load_stop_notification_count() const {
65    return load_stop_notification_count_;
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        base::FilePath(FILE_PATH_LITERAL("pdf_private")),
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->CopyFromBackingStore(
94        gfx::Rect(),
95        gfx::Size(),
96        base::Bind(&PDFBrowserTest::CopyFromBackingStoreCallback, this),
97        SkBitmap::kARGB_8888_Config);
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    base::string16 query = base::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 CopyFromBackingStoreCallback(bool success, const SkBitmap& bitmap) {
128    base::MessageLoopForUI::current()->Quit();
129    ASSERT_EQ(success, true);
130    base::FilePath reference = ui_test_utils::GetTestFilePath(
131        base::FilePath(FILE_PATH_LITERAL("pdf_private")),
132        base::FilePath().AppendASCII(expected_filename_));
133    base::File::Info info;
134    ASSERT_TRUE(base::GetFileInfo(reference, &info));
135    int size = static_cast<size_t>(info.size);
136    scoped_ptr<char[]> data(new char[size]);
137    ASSERT_EQ(size, base::ReadFile(reference, data.get(), size));
138
139    int w, h;
140    std::vector<unsigned char> decoded;
141    ASSERT_TRUE(gfx::PNGCodec::Decode(
142        reinterpret_cast<unsigned char*>(data.get()), size,
143        gfx::PNGCodec::FORMAT_BGRA, &decoded, &w, &h));
144    int32* ref_pixels = reinterpret_cast<int32*>(&decoded[0]);
145
146    int32* pixels = static_cast<int32*>(bitmap.getPixels());
147
148    // Get the background color, and use it to figure out the x-offsets in
149    // each image.  The reason is that depending on the theme in the OS, the
150    // same browser width can lead to slightly different plugin sizes, so the
151    // pdf content will start at different x offsets.
152    // Also note that the images we saved are cut off before the scrollbar, as
153    // that'll change depending on the theme, and also cut off vertically so
154    // that the ui controls don't show up, as those fade-in and so the timing
155    // will affect their transparency.
156    int32 bg_color = ref_pixels[0];
157    int ref_x_offset, snapshot_x_offset;
158    for (ref_x_offset = 0; ref_x_offset < w; ++ref_x_offset) {
159      if (ref_pixels[ref_x_offset] != bg_color)
160        break;
161    }
162
163    for (snapshot_x_offset = 0; snapshot_x_offset < bitmap.width();
164         ++snapshot_x_offset) {
165      if (pixels[snapshot_x_offset] != bg_color)
166        break;
167    }
168
169    int x_max = std::min(
170        w - ref_x_offset, bitmap.width() - snapshot_x_offset);
171    int y_max = std::min(h, bitmap.height());
172    int stride = bitmap.rowBytes();
173    snapshot_different_ = false;
174    for (int y = 0; y < y_max && !snapshot_different_; ++y) {
175      for (int x = 0; x < x_max && !snapshot_different_; ++x) {
176        if (pixels[y * stride / sizeof(int32) + x + snapshot_x_offset] !=
177            ref_pixels[y * w + x + ref_x_offset])
178          snapshot_different_ = true;
179      }
180    }
181
182    if (snapshot_different_) {
183      std::vector<unsigned char> png_data;
184      gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data);
185      if (base::CreateTemporaryFile(&snapshot_filename_)) {
186        base::WriteFile(snapshot_filename_,
187            reinterpret_cast<char*>(&png_data[0]), png_data.size());
188      }
189    }
190  }
191
192  // content::NotificationObserver
193  virtual void Observe(int type,
194                       const content::NotificationSource& source,
195                       const content::NotificationDetails& details) {
196    if (type == content::NOTIFICATION_LOAD_STOP) {
197      load_stop_notification_count_++;
198    }
199  }
200
201  // True if the snapshot differed from the expected value.
202  bool snapshot_different_;
203  // Internal variable used to synchronize to the renderer.
204  int next_dummy_search_value_;
205  // The filename of the bitmap to compare the snapshot to.
206  std::string expected_filename_;
207  // If the snapshot is different, holds the location where it's saved.
208  base::FilePath snapshot_filename_;
209  // How many times we've seen chrome::LOAD_STOP.
210  int load_stop_notification_count_;
211
212  net::test_server::EmbeddedTestServer pdf_test_server_;
213};
214
215
216// TODO(thestig): http://crbug.com/79837
217// Tests basic PDF rendering.  This can be broken depending on bad merges with
218// the vendor, so it's important that we have basic sanity checking.
219IN_PROC_BROWSER_TEST_F(PDFBrowserTest, DISABLED_Basic) {
220  ASSERT_NO_FATAL_FAILURE(Load());
221  ASSERT_NO_FATAL_FAILURE(WaitForResponse());
222  // OS X uses CoreText, and FreeType renders slightly different on Linux and
223  // Win.
224#if defined(OS_MACOSX)
225  // The bots render differently than locally, see http://crbug.com/142531.
226  ASSERT_TRUE(VerifySnapshot("pdf_browsertest_mac.png") ||
227              VerifySnapshot("pdf_browsertest_mac2.png"));
228#elif defined(OS_LINUX)
229  ASSERT_TRUE(VerifySnapshot("pdf_browsertest_linux.png"));
230#else
231  ASSERT_TRUE(VerifySnapshot("pdf_browsertest.png"));
232#endif
233}
234
235#if (!defined(GOOGLE_CHROME_BUILD) || defined(OS_CHROMEOS)) || \
236    (defined(OS_LINUX) || defined(OS_MACOSX))
237// TODO(sanjeevr): http://crbug.com/79837, http://crbug.com/332778
238#define MAYBE_Scroll DISABLED_Scroll
239#else
240#define MAYBE_Scroll Scroll
241#endif
242// Tests that scrolling works.
243IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_Scroll) {
244  ASSERT_NO_FATAL_FAILURE(Load());
245
246  // We use wheel mouse event since that's the only one we can easily push to
247  // the renderer.  There's no way to push a cross-platform keyboard event at
248  // the moment.
249  blink::WebMouseWheelEvent wheel_event;
250  wheel_event.type = blink::WebInputEvent::MouseWheel;
251  wheel_event.deltaY = -200;
252  wheel_event.wheelTicksY = -2;
253  WebContents* web_contents =
254      browser()->tab_strip_model()->GetActiveWebContents();
255  web_contents->GetRenderViewHost()->ForwardWheelEvent(wheel_event);
256  ASSERT_NO_FATAL_FAILURE(WaitForResponse());
257
258  int y_offset = 0;
259  ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
260      browser()->tab_strip_model()->GetActiveWebContents(),
261      "window.domAutomationController.send(plugin.pageYOffset())",
262      &y_offset));
263  ASSERT_GT(y_offset, 0);
264}
265
266#if (!defined(GOOGLE_CHROME_BUILD) || defined(OS_CHROMEOS)) || \
267    (defined(OS_LINUX) || defined(OS_MACOSX))
268// TODO(thestig): http://crbug.com/79837, http://crbug.com/329912
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      base::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  clipboard->Clear(ui::CLIPBOARD_TYPE_COPY_PASTE);
285
286  browser()->tab_strip_model()->GetActiveWebContents()->Copy();
287  ASSERT_NO_FATAL_FAILURE(WaitForResponse());
288
289  std::string text;
290  clipboard->ReadAsciiText(ui::CLIPBOARD_TYPE_COPY_PASTE, &text);
291  ASSERT_EQ("adipiscing", text);
292}
293
294const int kLoadingNumberOfParts = 10;
295
296// Tests that loading async pdfs works correctly (i.e. document fully loads).
297// This also loads all documents that used to crash, to ensure we don't have
298// regressions.
299// If it flakes, reopen http://crbug.com/74548.
300#if !defined(GOOGLE_CHROME_BUILD)
301#define MAYBE_Loading DISABLED_Loading
302#else
303#define MAYBE_Loading Loading
304#endif
305IN_PROC_BROWSER_TEST_P(PDFBrowserTest, MAYBE_Loading) {
306  ASSERT_TRUE(pdf_test_server()->InitializeAndWaitUntilReady());
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("/");
315
316  base::FileEnumerator file_enumerator(
317      ui_test_utils::GetTestFilePath(
318          base::FilePath(FILE_PATH_LITERAL("pdf_private")), 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#if defined(OS_MACOSX)
333    if (filename == "MVGD_sample_form.pdf")  // http://crbug.com/373619
334      continue;
335#endif
336
337    // Split the test into smaller sub-tests. Each one only loads
338    // every k-th file.
339    if (static_cast<int>(base::Hash(filename) % kLoadingNumberOfParts) !=
340        GetParam()) {
341      continue;
342    }
343
344    LOG(WARNING) << "PDFBrowserTest.Loading: " << filename;
345
346    GURL url = pdf_test_server()->GetURL(base_url + filename);
347    ui_test_utils::NavigateToURL(browser(), url);
348
349    while (true) {
350      int last_count = load_stop_notification_count();
351      // We might get extraneous chrome::LOAD_STOP notifications when
352      // doing async loading.  This happens when the first loader is cancelled
353      // and before creating a byte-range request loader.
354      bool complete = false;
355      ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
356          browser()->tab_strip_model()->GetActiveWebContents(),
357          "window.domAutomationController.send(plugin.documentLoadComplete())",
358          &complete));
359      if (complete)
360        break;
361
362      // Check if the LOAD_STOP notification could have come while we run a
363      // nested message loop for the JS call.
364      if (last_count != load_stop_notification_count())
365        continue;
366      content::WaitForLoadStop(
367          browser()->tab_strip_model()->GetActiveWebContents());
368    }
369  }
370}
371
372INSTANTIATE_TEST_CASE_P(PDFTestFiles,
373                        PDFBrowserTest,
374                        testing::Range(0, kLoadingNumberOfParts));
375
376#if !defined(GOOGLE_CHROME_BUILD) || defined(OS_MACOSX)
377// http://crbug.com/315160
378#define MAYBE_Action DISABLED_Action
379#else
380#define MAYBE_Action Action
381#endif
382IN_PROC_BROWSER_TEST_F(PDFBrowserTest, MAYBE_Action) {
383  ASSERT_NO_FATAL_FAILURE(Load());
384
385  ASSERT_TRUE(content::ExecuteScript(
386      browser()->tab_strip_model()->GetActiveWebContents(),
387      "document.getElementsByName('plugin')[0].fitToHeight();"));
388
389  std::string zoom1, zoom2;
390  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
391      browser()->tab_strip_model()->GetActiveWebContents(),
392      "window.domAutomationController.send("
393      "    document.getElementsByName('plugin')[0].getZoomLevel().toString())",
394      &zoom1));
395
396  ASSERT_TRUE(content::ExecuteScript(
397      browser()->tab_strip_model()->GetActiveWebContents(),
398      "document.getElementsByName('plugin')[0].fitToWidth();"));
399
400  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
401      browser()->tab_strip_model()->GetActiveWebContents(),
402      "window.domAutomationController.send("
403      "    document.getElementsByName('plugin')[0].getZoomLevel().toString())",
404      &zoom2));
405  ASSERT_NE(zoom1, zoom2);
406}
407
408// Flaky as per http://crbug.com/74549.
409IN_PROC_BROWSER_TEST_F(PDFBrowserTest, DISABLED_OnLoadAndReload) {
410  ASSERT_TRUE(pdf_test_server()->InitializeAndWaitUntilReady());
411
412  GURL url = pdf_test_server()->GetURL("/onload_reload.html");
413  ui_test_utils::NavigateToURL(browser(), url);
414
415  content::WindowedNotificationObserver observer(
416      content::NOTIFICATION_LOAD_STOP,
417      content::Source<NavigationController>(
418          &browser()->tab_strip_model()->GetActiveWebContents()->
419              GetController()));
420  ASSERT_TRUE(content::ExecuteScript(
421      browser()->tab_strip_model()->GetActiveWebContents(),
422      "reloadPDF();"));
423  observer.Wait();
424
425  ASSERT_EQ("success",
426            browser()->tab_strip_model()->GetActiveWebContents()->
427                GetURL().query());
428}
429
430}  // namespace
431