Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Side by Side Diff: chrome/browser/printing/print_preview_pdf_generated_browsertest.cc

Issue 335583004: Added a test that currently is able to print to pdf. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Refactored the browsertest. Fixed javascript bug where clicked was being used rather than checked. Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 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 <cstdio>
6 #include <iostream>
7 #include <limits>
8 #include <string>
9 #include <vector>
10
11 #include "base/file_util.h"
12 #include "base/files/file.h"
13 #include "base/files/file_path.h"
14 #include "base/files/scoped_temp_dir.h"
15 #include "base/logging.h"
16 #include "base/md5.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/numerics/safe_conversions.h"
19 #include "base/path_service.h"
20 #include "base/run_loop.h"
21 #include "base/scoped_native_library.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/string_util.h"
24 #include "chrome/app/chrome_command_ids.h"
25 #include "chrome/browser/net/referrer.h"
26 #include "chrome/browser/printing/print_preview_dialog_controller.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/ui/browser.h"
29 #include "chrome/browser/ui/browser_commands.h"
30 #include "chrome/browser/ui/tabs/tab_strip_model.h"
31 #include "chrome/browser/ui/webui/print_preview/print_preview_handler.h"
32 #include "chrome/browser/ui/webui/print_preview/print_preview_ui.h"
33 #include "chrome/browser/ui/webui/print_preview/sticky_settings.h"
34 #include "chrome/common/chrome_paths.h"
35 #include "chrome/common/print_messages.h"
36 #include "chrome/common/url_constants.h"
37 #include "chrome/test/base/in_process_browser_test.h"
38 #include "chrome/test/base/ui_test_utils.h"
39 #include "content/public/browser/web_contents.h"
40 #include "content/public/browser/web_ui_message_handler.h"
41 #include "content/public/common/page_transition_types.h"
42 #include "content/public/test/browser_test_utils.h"
43 #include "content/public/test/test_navigation_observer.h"
44 #include "content/public/test/test_utils.h"
45 #include "net/base/filename_util.h"
46 #include "printing/pdf_render_settings.h"
47 #include "printing/print_job_constants.h"
48 #include "ui/events/keycodes/keyboard_codes.h"
49 #include "ui/gfx/codec/png_codec.h"
50 #include "ui/gfx/geometry/rect.h"
51 #include "url/gurl.h"
52 #include "ipc/ipc_message_macros.h"
53
54 using content::WebContents;
55 using content::WebContentsObserver;
56
57 // Message refers to the 'UILoaded' message from print_preview.js.
58 // It gets sent either from onPreviewGenerationDone or from
59 // onManipulateSettings_() in print_preview.js
60 enum State {
61 // Waiting for the first message so the program can select Save as PDF
62 kWaitingToSendSaveAsPdf = 0,
63 // Waiting for the second message so the test can set the layout
64 kWaitingToSendLayoutSettings = 1,
65 // Waiting for the third message so the test can set the page numbers
66 kWaitingToSendPageNumbers = 2,
67 // Waiting for the forth message so the test can set the headers checkbox
68 kWaitingToSendHeadersAndFooters = 3,
69 // Waiting for the fifth message so the test can set the background checkbox
70 kWaitingToSendBackgroundColorsAndImages = 4,
71 // Waiting for the sixth message so the test can set the margins combobox
72 kWaitingToSendMargins = 5,
73 // Waiting for the final message so the program can save to PDF.
74 kWaitingForFinalMessage = 6,
75 };
76
77 struct PrintPreviewSettings {
78 PrintPreviewSettings() {}
Lei Zhang 2014/06/30 22:38:32 Do you need a default ctor? ... where the members
ivandavid 2014/07/03 03:12:02 At the time I did, however I rewrote the code so t
ivandavid 2014/07/03 03:12:02 Done.
79
80 PrintPreviewSettings(bool is_portrait,
81 std::string page_numbers,
82 bool headers_and_footers,
83 bool background_colors_and_images,
84 printing::MarginType margins)
85 : is_portrait_(is_portrait),
86 page_numbers_(page_numbers),
87 headers_and_footers_(headers_and_footers),
88 background_colors_and_images_(background_colors_and_images),
89 margins_(margins) {}
90
91 bool is_portrait_;
Lei Zhang 2014/06/30 22:38:32 struct members don't have the trailing underscore
ivandavid 2014/07/03 03:12:03 Done.
92 std::string page_numbers_;
93 bool headers_and_footers_;
94 bool background_colors_and_images_;
95 printing::MarginType margins_;
96 };
97
98 // Observes the print preview webpage. Once it observes the
99 // PreviewPageCount message, will send a sequence of commands
100 // to the print preview dialog and change the settings of the
101 // preview dialog.
102 class PrintPreviewObserver : public WebContentsObserver {
103 public:
104 PrintPreviewObserver(WebContents* dialog, Browser* browser)
105 : WebContentsObserver(dialog),
106 browser_(browser) {}
107
108 virtual ~PrintPreviewObserver() {}
109
110 // Sets closure for the observer so that it can end the loop.
111 void set_quit_closure(const base::Closure &closure) {
112 closure_ = closure;
113 }
114
115 // Actually stops the message_loop so that the test can proceed.
116 void EndLoop() {
117 base::MessageLoop::current()->PostTask(FROM_HERE, closure_);
118 }
119
120 // This method must always return false. If it ever returns true,
121 // it will cause the test to hang. This is because the
122 // PrintPreviewMessageHandler should still handle all of the messages to
123 // progress the print preview process.
124 bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
125 IPC_BEGIN_MESSAGE_MAP(PrintPreviewObserver, message)
126 IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetPreviewPageCount,
127 OnDidGetPreviewPageCount)
128 IPC_MESSAGE_UNHANDLED(break;)
129 IPC_END_MESSAGE_MAP();
130 return false;
131 }
132
133 // Gets the web contents for the print preview dialog so that
134 // the UI and other elements can be accessed.
135 WebContents* GetDialog() {
136 WebContents* tab = browser_->tab_strip_model()->GetActiveWebContents();
137 printing::PrintPreviewDialogController* dialog_controller =
138 printing::PrintPreviewDialogController::GetInstance();
139 WebContents* web_contents =
140 dialog_controller->GetPrintPreviewForContents(tab);
141 return web_contents;
142 }
143
144 // Gets the PrintPreviewUI so that certain elements can be accessed.
145 PrintPreviewUI* GetUI() {
146 return static_cast<PrintPreviewUI*>(
147 GetDialog()->GetWebUI()->GetController());
148 }
149
150 // Calls a javascript function that will change the print preview settings,
151 // such as the layout, the margins, page numbers, etc.
152 void ManipulatePreviewSettings(State state) {
153 scoped_ptr<base::DictionaryValue> script_argument(
154 new base::DictionaryValue());
155 std::vector<const base::Value*> args;
156 script_argument.reset(new base::DictionaryValue());
157 if (state == kWaitingToSendSaveAsPdf) {
158 script_argument->SetBoolean("selectSaveAsPdfDestination", true);
159 } else if (state == kWaitingToSendLayoutSettings) {
160 script_argument->SetBoolean("layoutSettings.portrait",
161 settings_.is_portrait_);
162 } else if (state == kWaitingToSendPageNumbers) {
163 script_argument->SetString("pageRange", settings_.page_numbers_);
164 } else if (state == kWaitingToSendHeadersAndFooters) {
165 script_argument->SetBoolean("headersAndFooters",
166 settings_.headers_and_footers_);
167 } else if (state == kWaitingToSendBackgroundColorsAndImages) {
168 script_argument->SetBoolean("backgroundColorsAndImages",
169 settings_.background_colors_and_images_);
170 } else if (state == kWaitingToSendMargins) {
171 script_argument->SetInteger("margins", settings_.margins_);
172 }
173
174 args.push_back(script_argument.get());
175 DCHECK(!script_argument->empty());
176 DCHECK(!args.empty());
177 GetUI()->web_ui()->CallJavascriptFunction(
Lei Zhang 2014/06/30 22:38:31 Since the size of |args| is only ever 1, you can d
ivandavid 2014/07/03 03:12:02 Done.
178 "onManipulateSettingsForTest", args);
179 }
180
181 // Function to set the print preview settings and save them so they
182 // can be sent later. Currently only used in the constructor. Will be
183 // used when creating a test and take command line arguments.
184 // |page_numbers| is a comma separated page range.
185 // Example: "1-5,9" will print pages 1 through 5 and page 9.
186 // The pages specified must be less than or equal to the maximum
187 // page number. An empty string seems to be valid input, however
188 // further testing will be required to see if that is actually
189 // true.
190 void SetPrintPreviewSettings(PrintPreviewSettings settings) {
Lei Zhang 2014/06/30 22:38:31 pass by const ref
ivandavid 2014/07/03 03:12:02 Done.
191 settings_ = settings;
192 }
193
194 private:
195 // Called when the observer gets the IPC message stating that the
196 // page count is ready.
197 // Due to forward declaration problem, the definition of the function
198 // must be separated from the declaration.
199 void OnDidGetPreviewPageCount(
200 const PrintHostMsg_DidGetPreviewPageCount_Params &params);
201
202 void DidCloneToNewWebContents(WebContents* old_web_contents,
203 WebContents* new_web_contents)
204 OVERRIDE {
205 Observe(new_web_contents);
206 }
207
208 void WebContentsDestroyed() OVERRIDE {
209 LOG(ERROR) << "DESTROYED";
Lei Zhang 2014/06/30 22:38:32 Accidentally included?
ivandavid 2014/07/03 03:12:02 Yeah. Done.
210 EndLoop();
211 }
212
213 Browser* browser_;
214 base::Closure closure_;
215 PrintPreviewSettings settings_;
216
217 DISALLOW_COPY_AND_ASSIGN(PrintPreviewObserver);
218 };
219
220 // Listens for messages from the print preview ui. Its a different
221 // set of messages, which is why two different classes are needed for this.
222 // When it gets the "UILoadedForTest" message, it prompts the observer to
223 // send a new message. When it gets the "UIFailedLoadingForTest" it just
224 // calls FAIL() and the test stops.
225 class UIDoneLoadingMessageHandler : public content::WebUIMessageHandler {
226 public:
227 explicit UIDoneLoadingMessageHandler(PrintPreviewObserver* observer) :
228 observer_(observer), state_(kWaitingToSendSaveAsPdf) {}
229
230 virtual ~UIDoneLoadingMessageHandler() {}
231
232 // When a setting has been set succesfully, this is called. If there
233 // are more settings to be set, ManipulatePreviewSettings on the observer
234 // is called to set the next settings. If there aren't anymore settings
235 // to be set, the loop that the observer is waiting in is ended.
236 void HandleDone(const base::ListValue* /* args */) {
237 if (state_ == kWaitingForFinalMessage) {
238 observer_->EndLoop();
239 } else {
240 observer_->ManipulatePreviewSettings(state_);
241 state_ = static_cast<State>(static_cast<int>(state_) + 1);
242 }
243 }
244
245 // Ends the test because a setting was not set successfully,
246 // therefore, the test shouldn't continue.
247 void HandleFailure(const base::ListValue* /* args */) {
248 FAIL();
249 }
250
251 // Sets up this class to listen for the UILoadedForTest and
252 // UIFailedLoadingForTest messages. These messages are sent
253 // by print_preview.js. On UILoadedForTest, a settings has
254 // been successfully set and its effects on the pdf have been finalized.
255 // On UIFaieldLoadingForTest a setting has not been successfully set
256 // and the test should fail.
257 void RegisterMessages() OVERRIDE {
258 web_ui()->RegisterMessageCallback(
259 "UILoadedForTest",
260 base::Bind(&UIDoneLoadingMessageHandler::HandleDone,
261 base::Unretained(this)));
262
263 web_ui()->RegisterMessageCallback(
264 "UIFailedLoadingForTest",
265 base::Bind(&UIDoneLoadingMessageHandler::HandleFailure,
266 base::Unretained(this)));
267 }
268
269 private:
270 PrintPreviewObserver* observer_;
271 State state_;
272
273 DISALLOW_COPY_AND_ASSIGN(UIDoneLoadingMessageHandler);
274 };
275
276 // This function needs to be forward declared.
277 void PrintPreviewObserver::OnDidGetPreviewPageCount(
278 const PrintHostMsg_DidGetPreviewPageCount_Params &params) {
279 WebContents* web_contents = GetDialog();
280 PrintPreviewUI* ui = GetUI();
281 ASSERT_TRUE(ui);
282 ASSERT_TRUE(ui->web_ui());
283 ui->web_ui()->CallJavascriptFunction("onEnableManipulateSettingsForTest");
284 Observe(web_contents);
285 ui->web_ui()->AddMessageHandler(new UIDoneLoadingMessageHandler(this));
286 }
287
288 class PrintPreviewPdfGeneratedBrowserTest : public InProcessBrowserTest {
289 public:
290 PrintPreviewPdfGeneratedBrowserTest() {}
291 virtual ~PrintPreviewPdfGeneratedBrowserTest() {}
292
293 // Navigates to the web page given, then initiates print preview
294 // and waits for all the settings to be set.
295 void NavigateAndPreview(const base::FilePath::StringType& file_name,
296 PrintPreviewSettings settings) {
297 print_preview_observer_->SetPrintPreviewSettings(settings);
298 base::FilePath path(file_name);
299 GURL gurl = net::FilePathToFileURL(path);
300
301 ui_test_utils::NavigateToURL(browser(),
302 gurl);
Lei Zhang 2014/06/30 22:38:31 fits on previous line
ivandavid 2014/07/03 03:12:02 Done.
303
304 base::RunLoop loop;
305 print_preview_observer_->set_quit_closure(loop.QuitClosure());
306 chrome::Print(browser());
307 loop.Run();
308 }
309
310 // Prints the webpage to pdf, after the settings have been set.
311 void Print(const base::FilePath& dir) {
312 pdf_file_save_path_ = dir.AppendASCII("dummy.pdf");
Lei Zhang 2014/06/30 22:38:32 Why not just set |pdf_file_save_path_| before you
ivandavid 2014/07/03 03:12:03 Done.
313 base::RunLoop loop;
314 print_preview_observer_->set_quit_closure(loop.QuitClosure());
315 print_preview_observer_->GetUI()->handler_->FileSelected(
316 pdf_file_save_path_, 0, NULL);
317 loop.Run();
318 }
319
320 // Converts the pdf to a a png file, so that the layout test can
Lei Zhang 2014/06/30 22:38:32 typo
ivandavid 2014/07/03 03:12:02 Done.
321 // do an image diff on this image and a reference image.
322 void PdfToPng() {
323 base::ScopedNativeLibrary pdf_lib;
324 base::FilePath pdf_module_path;
325
326 ASSERT_TRUE(PathService::Get(chrome::FILE_PDF_PLUGIN, &pdf_module_path));
327 ASSERT_TRUE(base::PathExists(pdf_module_path));
328 pdf_lib.Reset(base::LoadNativeLibrary(pdf_module_path, NULL));
329
330 LOG(ERROR) << pdf_module_path.value();
Lei Zhang 2014/06/30 22:38:31 remove
ivandavid 2014/07/03 03:12:02 Done.
331
332 ASSERT_TRUE(pdf_lib.is_valid());
333 pdf_to_bitmap_func_ =
334 reinterpret_cast<PDFPageToBitmap>(
335 pdf_lib.GetFunctionPointer("RenderPDFPageToBitmap"));
336
337 ASSERT_TRUE(pdf_to_bitmap_func_);
338
339
340 std::string data;
341 gfx::Rect rect(800, 800);
342
343 ASSERT_TRUE(base::ReadFileToString(pdf_file_save_path_, &data));
344 printing::PdfRenderSettings settings(rect, 300, true);
345
346 // Multiple by 4 b/c that's the # of color channels.
347 scoped_ptr<uint8_t> bitmap_data(
348 new uint8_t[4 * settings.area().size().GetArea()]);
349
350 // Just print a single page for now, print all in the future.
351 // The pages that are to be tested are selected when
352 // sending messages to print preview.
353 ASSERT_TRUE(pdf_to_bitmap_func_(data.data(),
354 data.size(),
355 0,
356 bitmap_data.get(),
357 settings.area().size().width(),
358 settings.area().size().height(),
359 settings.dpi(),
360 settings.dpi(),
361 true));
362
363 std::vector<gfx::PNGCodec::Comment> comments;
364 ASSERT_TRUE(
365 gfx::PNGCodec::Encode(static_cast<unsigned char*>(
366 bitmap_data.get()),
367 gfx::PNGCodec::FORMAT_BGRA,
368 settings.area().size(),
369 settings.area().size().width() * sizeof(uint32_t),
370 false,
371 comments,
372 &output_));
373
374 std::string hash_input(output_.begin(), output_.end());
375
376 base::MD5Sum(hash_input.data(),
377 hash_input.size(),
Lei Zhang 2014/06/30 22:38:31 this all fits on 1 line?
ivandavid 2014/07/03 03:12:03 Done.
378 &hash_);
379
380 std::string comment_title("tEXtchecksum\x00");
381 gfx::PNGCodec::Comment hash_comment(
382 comment_title,
383 base::MD5DigestToBase16(hash_));
384
385 comments.push_back(hash_comment);
386
387 // Have to do it twice, b/c we need to get the hash of the png
388 // then actually write the hash into the png. Probably not
389 // the correct way to do this, but for now it'll do as long as
390 // it passes the test. Find out how content shell does it and then
391 // emulate it. The hash's don't have to be equal for the test to
392 // succeed, only the images do, however if the hashes
393 // are the same, there is no point in doing image_diff since its
394 // essentially the same file.
395 ASSERT_TRUE(
396 gfx::PNGCodec::Encode(static_cast<unsigned char*>(
397 bitmap_data.get()),
398 gfx::PNGCodec::FORMAT_BGRA,
399 settings.area().size(),
400 settings.area().size().width() * sizeof(uint32_t),
401 false,
402 comments,
403 &output_));
404 }
405
406 // Sends the png image to the layout test framework for comparison.
407 void SendPng() {
408 // Send image header & hash_
409 printf("Content-Type: image/png\n");
410 printf("ActualHash: %s\n", base::MD5DigestToBase16(hash_).data());
411 printf("Content-Length: %lu\n", output_.size());
Lei Zhang 2014/06/30 22:38:32 You probably need the PRIuS macro from base/format
ivandavid 2014/07/03 03:12:03 Done.
412 std::vector<unsigned char>::iterator it = output_.begin();
Lei Zhang 2014/06/30 22:38:32 For vectors, if you write: for (size_t i = 0; i <
ivandavid 2014/07/03 03:12:02 Done.
413 std::vector<unsigned char>::iterator end = output_.end();
414
415 for ( ; it != end; it++)
416 printf("%c", *it);
417
418 printf("#EOF\n");
419 fflush(stdout);
420 fprintf(stderr, "#EOF\n");
421 fflush(stderr);
422 }
423
424 // Duplicates the tab that was created when the browser opened.
425 // This is done, so that the observer can listen to the duplicated
426 // tab as soon as possible and start listening for messages related to
427 // print preview.
428 void DuplicateTab() {
429 WebContents* tab =
430 browser()->tab_strip_model()->GetActiveWebContents();
431 ASSERT_TRUE(tab);
432
433 print_preview_observer_.reset(new PrintPreviewObserver(tab, browser()));
434 chrome::DuplicateTab(browser());
435
436 WebContents* initiator =
437 browser()->tab_strip_model()->GetActiveWebContents();
438 ASSERT_TRUE(initiator);
439 ASSERT_NE(tab, initiator);
440 }
441
442 // Resets the test so that another web page can be printing.
443 // Deletes the duplicate tab as it isn't needed anymore.
444 void Clean() {
445 output_.clear();
446 ASSERT_TRUE(browser()->tab_strip_model()->count() == 2);
Lei Zhang 2014/06/30 22:38:31 ASSERT_EQ
ivandavid 2014/07/03 03:12:02 Done.
447 chrome::CloseTab(browser());
448 ASSERT_TRUE(browser()->tab_strip_model()->count() == 1);
449 pdf_file_save_path_.clear();
450 }
451
452 private:
453 scoped_ptr<PrintPreviewObserver> print_preview_observer_;
454 base::FilePath pdf_file_save_path_;
455
456 typedef bool (*PDFPageToBitmap) (const void * pdf_buffer,
457 int pdf_buffer_size,
458 int page_number,
459 void* bitmap_buffer,
460 int bitmap_width,
461 int bitmap_height,
462 int dpi_x,
463 int dpi_y,
464 bool autorotate);
465
466 typedef int (*PageSizeByIndex) (const void * pdf_buffer,
467 int page_index,
468 double* width,
469 double* height);
470
471 PDFPageToBitmap pdf_to_bitmap_func_;
472 PageSizeByIndex pdf_page_size_func_;
473 std::vector<unsigned char> output_;
474 base::MD5Digest hash_;
475
476 DISALLOW_COPY_AND_ASSIGN(PrintPreviewPdfGeneratedBrowserTest);
477 };
478
479 IN_PROC_BROWSER_TEST_F(PrintPreviewPdfGeneratedBrowserTest,
480 DISABLED_DummyTest) {
481 // What this code is supposed to do: Setup communication with the
482 // layout test framework, print webpage to a pdf, convert that
483 // pdf to a png, then send that png file to the layout test framework,
484 // where the framework will do an image diff.
485 printf("#READY\n");
486 fflush(stdout);
487
488 base::ScopedTempDir tmp_dir;
489 base::FilePath tmp_path;
490
491 ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
492 ASSERT_TRUE(base::CreateTemporaryFileInDir(tmp_dir.path(), &tmp_path));
493 ASSERT_TRUE(freopen(tmp_path.value().c_str(), "r", stdin));
494
495 printf("StdinPath: %s\n#EOF\n", tmp_path.value().c_str());
496 fflush(stdout);
497
498 while (true) {
499 DuplicateTab();
500
501 base::FilePath::StringType cmd;
Lei Zhang 2014/06/30 22:38:31 I don't think std::getline() takes a std::wstring
ivandavid 2014/07/03 03:12:02 I think I fixed it. I defined a macro STDIN_STREAM
502 std::getline(std::cin, cmd);
503 if (cmd.size() == 0) {
Lei Zhang 2014/06/30 22:38:32 .empty()
ivandavid 2014/07/03 03:12:02 Done.
504 while (std::cin.eof()) {
505 std::cin.clear();
506 std::getline(std::cin, cmd);
507 if (!cmd.empty()) {
508 break;
509 }
510 }
511 }
512
513 // TODO(ivandavid): Have the layout test framework read a settings file,
514 // and have those settings be place in this settings struct.
515 PrintPreviewSettings settings(true,
516 "",
517 false,
518 false,
519 printing::DEFAULT_MARGINS);
520
521 std::vector<base::FilePath::StringType> cmd_arguments;
522 base::SplitString(cmd, '\'', &cmd_arguments);
523 base::FilePath::StringType test_name(cmd_arguments[0]);
524 NavigateAndPreview(test_name, settings);
525 Print(tmp_dir.path());
526 PdfToPng();
527
528 printf("#EOF\n");
529 fflush(stdout);
530
531 SendPng();
532 Clean();
533 }
534 fclose(stdin);
535 base::DeleteFile(tmp_path, false);
Lei Zhang 2014/06/30 22:38:32 You don't need this. Since |tmp_path| is within |t
ivandavid 2014/07/03 03:12:02 Done.
536 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698