• 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/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:
PDFBrowserTest()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.
pdf_test_server()62   net::test_server::EmbeddedTestServer* pdf_test_server() {
63     return &pdf_test_server_;
64   }
65 
load_stop_notification_count() const66   int load_stop_notification_count() const {
67     return load_stop_notification_count_;
68   }
69 
Load()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 
VerifySnapshot(const std::string & expected_filename)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 
WaitForResponse()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:
CopyFromBackingStoreCallback(bool success,const SkBitmap & bitmap)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
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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
SetUpCommandLine(base::CommandLine * command_line)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
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Basic)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.
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Scroll)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
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,DISABLED_FindAndCopy)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
IN_PROC_BROWSER_TEST_P(PDFBrowserTest,MAYBE_Loading)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
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_Action)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
IN_PROC_BROWSER_TEST_F(PDFBrowserTest,MAYBE_OnLoadAndReload)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