• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
PDFBrowserTest()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.
pdf_test_server()55   net::test_server::EmbeddedTestServer* pdf_test_server() {
56     return &pdf_test_server_;
57   }
58 
load_stop_notification_count() const59   int load_stop_notification_count() const {
60     return load_stop_notification_count_;
61   }
62 
GetPDFTestDir()63   base::FilePath GetPDFTestDir() {
64     return base::FilePath(base::FilePath::kCurrentDirectory).AppendASCII("..").
65         AppendASCII("..").AppendASCII("..").AppendASCII("pdf").
66         AppendASCII("test");
67   }
68 
Load()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 
VerifySnapshot(const std::string & expected_filename)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 
WaitForResponse()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:
GetSnapshotFromRendererCallback(bool success,const SkBitmap & bitmap)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
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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.
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Basic)222 IN_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.
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Scroll)245 IN_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
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_FindAndCopy)274 IN_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 
300 const 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.
IN_PROC_BROWSER_TEST_P(PDFBrowserTest,Loading)306 IN_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 
368 INSTANTIATE_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
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Action)378 IN_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.
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,DISABLED_OnLoadAndReload)405 IN_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