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