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