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