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 <stdint.h>
6 
7 #include <algorithm>
8 #include <fstream>
9 #include <iostream>
10 #include <iterator>
11 #include <limits>
12 #include <memory>
13 #include <string>
14 #include <utility>
15 #include <vector>
16 
17 #include "base/bind.h"
18 #include "base/callback.h"
19 #include "base/containers/span.h"
20 #include "base/files/file.h"
21 #include "base/files/file_path.h"
22 #include "base/files/file_util.h"
23 #include "base/files/scoped_temp_dir.h"
24 #include "base/hash/md5.h"
25 #include "base/location.h"
26 #include "base/macros.h"
27 #include "base/optional.h"
28 #include "base/path_service.h"
29 #include "base/run_loop.h"
30 #include "base/single_thread_task_runner.h"
31 #include "base/strings/string_split.h"
32 #include "base/strings/utf_string_conversions.h"
33 #include "base/threading/thread_task_runner_handle.h"
34 #include "build/build_config.h"
35 #include "chrome/browser/printing/print_preview_dialog_controller.h"
36 #include "chrome/browser/ui/browser.h"
37 #include "chrome/browser/ui/browser_commands.h"
38 #include "chrome/browser/ui/tabs/tab_strip_model.h"
39 #include "chrome/browser/ui/webui/print_preview/print_preview_ui.h"
40 #include "chrome/common/chrome_paths.h"
41 #include "chrome/test/base/in_process_browser_test.h"
42 #include "chrome/test/base/ui_test_utils.h"
43 #include "components/printing/common/print.mojom.h"
44 #include "components/printing/common/print_messages.h"
45 #include "content/public/browser/web_contents.h"
46 #include "content/public/browser/web_ui_message_handler.h"
47 #include "content/public/test/browser_test.h"
48 #include "content/public/test/browser_test_utils.h"
49 #include "ipc/ipc_message_macros.h"
50 #include "net/base/filename_util.h"
51 #include "pdf/pdf.h"
52 #include "printing/mojom/print.mojom.h"
53 #include "printing/pdf_render_settings.h"
54 #include "printing/units.h"
55 #include "ui/gfx/codec/png_codec.h"
56 #include "ui/gfx/geometry/point.h"
57 #include "ui/gfx/geometry/rect.h"
58 #include "ui/gfx/geometry/size_f.h"
59 #include "url/gurl.h"
60 
61 #if defined(OS_WIN)
62 #include <fcntl.h>
63 #include <io.h>
64 #endif
65 
66 using content::WebContents;
67 using content::WebContentsObserver;
68 
69 namespace printing {
70 
71 // Number of color channels in a BGRA bitmap.
72 const int kColorChannels = 4;
73 const int kDpi = 300;
74 
75 // Every state is used when the document is a non-PDF source. When the source is
76 // a PDF, kWaitingToSendSaveAsPDF, kWaitingToSendPageNumbers, and
77 // kWaitingForFinalMessage are the only states used.
78 enum State {
79   // Waiting for the first message so the program can select Save as PDF
80   kWaitingToSendSaveAsPdf = 0,
81   // Waiting for the second message so the test can set the layout
82   kWaitingToSendLayoutSettings = 1,
83   // Waiting for the third message so the test can set the page numbers
84   kWaitingToSendPageNumbers = 2,
85   // Waiting for the forth message so the test can set the headers checkbox
86   kWaitingToSendHeadersAndFooters = 3,
87   // Waiting for the fifth message so the test can set the background checkbox
88   kWaitingToSendBackgroundColorsAndImages = 4,
89   // Waiting for the sixth message so the test can set the margins combobox
90   kWaitingToSendMargins = 5,
91   // Waiting for the final message so the program can save to PDF.
92   kWaitingForFinalMessage = 6,
93 };
94 
95 // Settings for print preview. It reflects the current options provided by
96 // print preview. If more options are added, more states should be added and
97 // there should be more settings added to this struct.
98 struct PrintPreviewSettings {
PrintPreviewSettingsprinting::PrintPreviewSettings99   PrintPreviewSettings(bool is_portrait,
100                        const std::string& page_numbers,
101                        bool headers_and_footers,
102                        bool background_colors_and_images,
103                        mojom::MarginType margins,
104                        bool source_is_pdf)
105       : is_portrait(is_portrait),
106         page_numbers(page_numbers),
107         headers_and_footers(headers_and_footers),
108         background_colors_and_images(background_colors_and_images),
109         margins(margins),
110         source_is_pdf(source_is_pdf) {}
111 
112   bool is_portrait;
113   std::string page_numbers;
114   bool headers_and_footers;
115   bool background_colors_and_images;
116   mojom::MarginType margins;
117   bool source_is_pdf;
118 };
119 
120 // Observes the print preview webpage. Once it observes the PreviewPageCount
121 // message, will send a sequence of commands to the print preview dialog and
122 // change the settings of the preview dialog.
123 class PrintPreviewObserver : public WebContentsObserver {
124  public:
PrintPreviewObserver(Browser * browser,WebContents * dialog,const base::FilePath & pdf_file_save_path)125   PrintPreviewObserver(Browser* browser,
126                        WebContents* dialog,
127                        const base::FilePath& pdf_file_save_path)
128       : WebContentsObserver(dialog),
129         browser_(browser),
130         state_(kWaitingToSendSaveAsPdf),
131         failed_setting_("None"),
132         pdf_file_save_path_(pdf_file_save_path) {}
133 
~PrintPreviewObserver()134   ~PrintPreviewObserver() override {}
135 
136   // Sets closure for the observer so that it can end the loop.
set_quit_closure(base::OnceClosure closure)137   void set_quit_closure(base::OnceClosure closure) {
138     quit_closure_ = std::move(closure);
139   }
140 
141   // Actually stops the message loop so that the test can proceed.
EndLoop()142   void EndLoop() {
143     base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
144                                                   std::move(quit_closure_));
145   }
146 
OnMessageReceived(const IPC::Message & message)147   bool OnMessageReceived(const IPC::Message& message) override {
148     IPC_BEGIN_MESSAGE_MAP(PrintPreviewObserver, message)
149       IPC_MESSAGE_HANDLER(PrintHostMsg_DidStartPreview, OnDidStartPreview)
150     IPC_END_MESSAGE_MAP()
151     return false;
152   }
153 
154   // Gets the web contents for the print preview dialog so that the UI and
155   // other elements can be accessed.
GetDialog()156   WebContents* GetDialog() {
157     WebContents* tab = browser_->tab_strip_model()->GetActiveWebContents();
158     PrintPreviewDialogController* dialog_controller =
159         PrintPreviewDialogController::GetInstance();
160     return dialog_controller->GetPrintPreviewForContents(tab);
161   }
162 
163   // Gets the PrintPreviewUI so that certain elements can be accessed.
GetUI()164   PrintPreviewUI* GetUI() {
165     return static_cast<PrintPreviewUI*>(
166         GetDialog()->GetWebUI()->GetController());
167   }
168 
169   // Calls native_layer.onManipulateSettingsForTest() and sends a dictionary
170   // value containing the type of setting and the value to set that settings
171   // to.
ManipulatePreviewSettings()172   void ManipulatePreviewSettings() {
173     base::DictionaryValue script_argument;
174 
175     if (state_ == kWaitingToSendSaveAsPdf) {
176       script_argument.SetBoolean("selectSaveAsPdfDestination", true);
177       state_ = settings_->source_is_pdf ?
178                kWaitingToSendPageNumbers : kWaitingToSendLayoutSettings;
179       failed_setting_ = "Save as PDF";
180     } else if (state_ == kWaitingToSendLayoutSettings) {
181       script_argument.SetBoolean("layoutSettings.portrait",
182                                  settings_->is_portrait);
183       state_ = kWaitingToSendPageNumbers;
184       failed_setting_ = "Layout Settings";
185     } else if (state_ == kWaitingToSendPageNumbers) {
186       script_argument.SetString("pageRange", settings_->page_numbers);
187       state_ = settings_->source_is_pdf ?
188                kWaitingForFinalMessage : kWaitingToSendHeadersAndFooters;
189       failed_setting_ = "Page Range";
190     } else if (state_ == kWaitingToSendHeadersAndFooters) {
191       script_argument.SetBoolean("headersAndFooters",
192                                  settings_->headers_and_footers);
193       state_ = kWaitingToSendBackgroundColorsAndImages;
194       failed_setting_ = "Headers and Footers";
195     } else if (state_ == kWaitingToSendBackgroundColorsAndImages) {
196       script_argument.SetBoolean("backgroundColorsAndImages",
197                                  settings_->background_colors_and_images);
198       state_ = kWaitingToSendMargins;
199       failed_setting_ = "Background Colors and Images";
200     } else if (state_ == kWaitingToSendMargins) {
201       script_argument.SetInteger("margins",
202                                  static_cast<int>(settings_->margins));
203       state_ = kWaitingForFinalMessage;
204       failed_setting_ = "Margins";
205     } else if (state_ == kWaitingForFinalMessage) {
206       // Called by |GetUI()->handler_|, it is a callback function that call
207       // |EndLoop| when an attempt to save the PDF has been made.
208       GetUI()->SetPdfSavedClosureForTesting(base::BindOnce(
209           &PrintPreviewObserver::EndLoop, base::Unretained(this)));
210       ASSERT_FALSE(pdf_file_save_path_.empty());
211       GetUI()->SetSelectedFileForTesting(pdf_file_save_path_);
212       return;
213     }
214 
215     ASSERT_FALSE(script_argument.empty());
216     GetUI()->SendManipulateSettingsForTest(script_argument);
217   }
218 
219   // Saves the print preview settings to be sent to the print preview dialog.
SetPrintPreviewSettings(const PrintPreviewSettings & settings)220   void SetPrintPreviewSettings(const PrintPreviewSettings& settings) {
221     settings_ = std::make_unique<PrintPreviewSettings>(settings);
222   }
223 
224   // Returns the setting that could not be set in the preview dialog.
GetFailedSetting() const225   const std::string& GetFailedSetting() const {
226     return failed_setting_;
227   }
228 
229  private:
230   // Listens for messages from the print preview dialog. Specifically, it
231   // listens for 'UILoadedForTest' and 'UIFailedLoadingForTest.'
232   class UIDoneLoadingMessageHandler : public content::WebUIMessageHandler {
233    public:
UIDoneLoadingMessageHandler(PrintPreviewObserver * observer)234     explicit UIDoneLoadingMessageHandler(PrintPreviewObserver* observer)
235         : observer_(observer) {}
236 
~UIDoneLoadingMessageHandler()237     ~UIDoneLoadingMessageHandler() override {}
238 
239     // When a setting has been set succesfully, this is called and the observer
240     // is told to send the next setting to be set.
HandleDone(const base::ListValue *)241     void HandleDone(const base::ListValue* /* args */) {
242       observer_->ManipulatePreviewSettings();
243     }
244 
245     // Ends the test because a setting was not set successfully. Called when
246     // this class hears 'UIFailedLoadingForTest.'
HandleFailure(const base::ListValue *)247     void HandleFailure(const base::ListValue* /* args */) {
248       FAIL() << "Failed to set: " << observer_->GetFailedSetting();
249     }
250 
251     // Allows this class to listen for the 'UILoadedForTest' and
252     // 'UIFailedLoadingForTest' messages. These messages are sent by the print
253     // preview dialog. 'UILoadedForTest' is sent when a setting has been
254     // successfully set and its effects have been finalized.
255     // 'UIFailedLoadingForTest' is sent when the setting could not be set. This
256     // causes the browser test to fail.
RegisterMessages()257     void RegisterMessages() override {
258       web_ui()->RegisterMessageCallback(
259           "UILoadedForTest",
260           base::BindRepeating(&UIDoneLoadingMessageHandler::HandleDone,
261                               base::Unretained(this)));
262 
263       web_ui()->RegisterMessageCallback(
264           "UIFailedLoadingForTest",
265           base::BindRepeating(&UIDoneLoadingMessageHandler::HandleFailure,
266                               base::Unretained(this)));
267     }
268 
269    private:
270     PrintPreviewObserver* const observer_;
271 
272     DISALLOW_COPY_AND_ASSIGN(UIDoneLoadingMessageHandler);
273   };
274 
275   // Called when the observer gets the IPC message with the preview document's
276   // properties.
OnDidStartPreview(const mojom::DidStartPreviewParams & params,const printing::mojom::PreviewIds & ids)277   void OnDidStartPreview(const mojom::DidStartPreviewParams& params,
278                          const printing::mojom::PreviewIds& ids) {
279     WebContents* web_contents = GetDialog();
280     ASSERT_TRUE(web_contents);
281     Observe(web_contents);
282 
283     PrintPreviewUI* ui = GetUI();
284     ASSERT_TRUE(ui);
285     ASSERT_TRUE(ui->web_ui());
286 
287     ui->web_ui()->AddMessageHandler(
288         std::make_unique<UIDoneLoadingMessageHandler>(this));
289     ui->SendEnableManipulateSettingsForTest();
290   }
291 
DidCloneToNewWebContents(WebContents * old_web_contents,WebContents * new_web_contents)292   void DidCloneToNewWebContents(WebContents* old_web_contents,
293                                 WebContents* new_web_contents) override {
294     Observe(new_web_contents);
295   }
296 
297   Browser* browser_;
298   base::OnceClosure quit_closure_;
299   std::unique_ptr<PrintPreviewSettings> settings_;
300 
301   // State of the observer. The state indicates what message to send
302   // next. The state advances whenever the message handler calls
303   // ManipulatePreviewSettings() on the observer.
304   State state_;
305   std::string failed_setting_;
306   const base::FilePath pdf_file_save_path_;
307 
308   DISALLOW_COPY_AND_ASSIGN(PrintPreviewObserver);
309 };
310 
311 class PrintPreviewPdfGeneratedBrowserTest : public InProcessBrowserTest {
312  public:
PrintPreviewPdfGeneratedBrowserTest()313   PrintPreviewPdfGeneratedBrowserTest() {}
~PrintPreviewPdfGeneratedBrowserTest()314   ~PrintPreviewPdfGeneratedBrowserTest() override {}
315 
316   // Navigates to the given web page, then initiates print preview and waits
317   // for all the settings to be set, then save the preview to PDF.
NavigateAndPrint(const base::FilePath::StringType & file_name,const PrintPreviewSettings & settings)318   void NavigateAndPrint(const base::FilePath::StringType& file_name,
319                           const PrintPreviewSettings& settings) {
320     print_preview_observer_->SetPrintPreviewSettings(settings);
321     base::FilePath path(file_name);
322     GURL gurl = net::FilePathToFileURL(base::MakeAbsoluteFilePath(path));
323 
324     ui_test_utils::NavigateToURL(browser(), gurl);
325 
326     base::RunLoop loop;
327     print_preview_observer_->set_quit_closure(loop.QuitClosure());
328     chrome::Print(browser());
329     loop.Run();
330 
331     // Need to check whether the save was successful. Ending the loop only
332     // means the save was attempted.
333     base::File pdf_file(
334         pdf_file_save_path_, base::File::FLAG_OPEN | base::File::FLAG_READ);
335     ASSERT_TRUE(pdf_file.IsValid());
336   }
337 
338   // Converts the PDF to a PNG file so that the layout test can do an image
339   // diff on this image and a reference image.
PdfToPng()340   void PdfToPng() {
341     int num_pages;
342     float max_width_in_points = 0;
343     std::vector<uint8_t> bitmap_data;
344     double total_height_in_pixels = 0;
345     std::string pdf_data;
346 
347     ASSERT_TRUE(base::ReadFileToString(pdf_file_save_path_, &pdf_data));
348 
349     auto pdf_span = base::as_bytes(base::make_span(pdf_data));
350     ASSERT_TRUE(
351         chrome_pdf::GetPDFDocInfo(pdf_span, &num_pages, &max_width_in_points));
352 
353     ASSERT_GT(num_pages, 0);
354     double max_width_in_pixels =
355         ConvertUnitDouble(max_width_in_points, kPointsPerInch, kDpi);
356 
357     constexpr chrome_pdf::RenderOptions options = {
358         .stretch_to_bounds = false,
359         .keep_aspect_ratio = true,
360         .autorotate = false,
361         .use_color = true,
362         .render_device_type = chrome_pdf::RenderDeviceType::kPrinter,
363     };
364     for (int i = 0; i < num_pages; ++i) {
365       base::Optional<gfx::SizeF> size_in_points =
366           chrome_pdf::GetPDFPageSizeByIndex(pdf_span, i);
367       ASSERT_TRUE(size_in_points.has_value());
368 
369       double width_in_pixels = ConvertUnitDouble(size_in_points.value().width(),
370                                                  kPointsPerInch, kDpi);
371       double height_in_pixels = ConvertUnitDouble(
372           size_in_points.value().height(), kPointsPerInch, kDpi);
373 
374       // The image will be rotated if |width_in_pixels| is greater than
375       // |height_in_pixels|. This is because the page will be rotated to fit
376       // within a piece of paper. Therefore, |width_in_pixels| and
377       // |height_in_pixels| have to be swapped or else they won't reflect the
378       // dimensions of the rotated page.
379       if (width_in_pixels > height_in_pixels)
380         std::swap(width_in_pixels, height_in_pixels);
381 
382       total_height_in_pixels += height_in_pixels;
383       gfx::Rect rect(width_in_pixels, height_in_pixels);
384 
385       PdfRenderSettings settings(rect, gfx::Point(), gfx::Size(kDpi, kDpi),
386                                  options.autorotate, options.use_color,
387                                  PdfRenderSettings::Mode::NORMAL);
388 
389       int int_max = std::numeric_limits<int>::max();
390       if (settings.area.width() > int_max / kColorChannels ||
391           settings.area.height() >
392               int_max / (kColorChannels * settings.area.width())) {
393         FAIL() << "The dimensions of the image are too large."
394                << "Decrease the DPI or the dimensions of the image.";
395       }
396 
397       std::vector<uint8_t> page_bitmap_data(kColorChannels *
398                                             settings.area.size().GetArea());
399 
400       ASSERT_TRUE(chrome_pdf::RenderPDFPageToBitmap(
401           pdf_span, i, page_bitmap_data.data(), settings.area.size(),
402           settings.dpi, options));
403       FillPng(&page_bitmap_data, width_in_pixels, max_width_in_pixels,
404               settings.area.size().height());
405       bitmap_data.insert(bitmap_data.end(),
406                          page_bitmap_data.begin(),
407                          page_bitmap_data.end());
408     }
409 
410     CreatePng(bitmap_data, max_width_in_pixels, total_height_in_pixels);
411   }
412 
413   // Fills out a bitmap with whitespace so that the image will correctly fit
414   // within a PNG that is wider than the bitmap itself.
FillPng(std::vector<uint8_t> * bitmap,int current_width,int desired_width,int height)415   void FillPng(std::vector<uint8_t>* bitmap,
416                int current_width,
417                int desired_width,
418                int height) {
419     ASSERT_TRUE(bitmap);
420     ASSERT_GT(height, 0);
421     ASSERT_LE(current_width, desired_width);
422 
423     if (current_width == desired_width)
424       return;
425 
426     int current_width_in_bytes = current_width * kColorChannels;
427     int desired_width_in_bytes = desired_width * kColorChannels;
428 
429     // The color format is BGRA, so to set the color to white, every pixel is
430     // set to 0xFFFFFFFF.
431     const uint8_t kColorByte = 255;
432     std::vector<uint8_t> filled_bitmap(
433         desired_width * kColorChannels * height, kColorByte);
434     auto filled_bitmap_it = filled_bitmap.begin();
435     auto bitmap_it = bitmap->begin();
436 
437     for (int i = 0; i < height; ++i) {
438       std::copy(
439           bitmap_it, bitmap_it + current_width_in_bytes, filled_bitmap_it);
440       std::advance(bitmap_it, current_width_in_bytes);
441       std::advance(filled_bitmap_it, desired_width_in_bytes);
442     }
443 
444     bitmap->assign(filled_bitmap.begin(), filled_bitmap.end());
445   }
446 
447   // Sends the PNG image to the layout test framework for comparison.
SendPng()448   void SendPng() {
449     // Send image header and |hash_| to the layout test framework.
450     std::cout << "Content-Type: image/png\n";
451     std::cout << "ActualHash: " << base::MD5DigestToBase16(hash_) << "\n";
452     std::cout << "Content-Length: " << png_output_.size() << "\n";
453 
454     std::copy(png_output_.begin(),
455               png_output_.end(),
456               std::ostream_iterator<unsigned char>(std::cout, ""));
457 
458     std::cout << "#EOF\n";
459     std::cout.flush();
460     std::cerr << "#EOF\n";
461     std::cerr.flush();
462   }
463 
464   // Duplicates the tab that was created when the browser opened. This is done
465   // so that the observer can listen to the duplicated tab as soon as possible
466   // and start listening for messages related to print preview.
DuplicateTab()467   void DuplicateTab() {
468     WebContents* tab =
469         browser()->tab_strip_model()->GetActiveWebContents();
470     ASSERT_TRUE(tab);
471 
472     print_preview_observer_ = std::make_unique<PrintPreviewObserver>(
473         browser(), tab, pdf_file_save_path_);
474     chrome::DuplicateTab(browser());
475 
476     WebContents* initiator =
477         browser()->tab_strip_model()->GetActiveWebContents();
478     ASSERT_TRUE(initiator);
479     ASSERT_NE(tab, initiator);
480   }
481 
482   // Resets the test so that another web page can be printed. It also deletes
483   // the duplicated tab as it isn't needed anymore.
Reset()484   void Reset() {
485     png_output_.clear();
486     ASSERT_EQ(2, browser()->tab_strip_model()->count());
487     chrome::CloseTab(browser());
488     ASSERT_EQ(1, browser()->tab_strip_model()->count());
489   }
490 
491   // Creates a temporary directory to store a text file that will be used for
492   // stdin to accept input from the layout test framework. A path for the PDF
493   // file is also created. The directory and files within it are automatically
494   // cleaned up once the test ends.
SetupStdinAndSavePath()495   void SetupStdinAndSavePath() {
496     // Sets the filemode to binary because it will force |std::cout| to send LF
497     // rather than CRLF. Sending CRLF will cause an error message for the
498     // layout tests.
499 #if defined(OS_WIN)
500     _setmode(_fileno(stdout), _O_BINARY);
501     _setmode(_fileno(stderr), _O_BINARY);
502 #endif
503     // Sends a message to the layout test framework indicating indicating
504     // that the browser test has completed setting itself up. The layout
505     // test will then expect the file path for stdin.
506     base::FilePath stdin_path;
507     std::cout << "#READY\n";
508     std::cout.flush();
509 
510     ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
511     ASSERT_TRUE(
512         base::CreateTemporaryFileInDir(tmp_dir_.GetPath(), &stdin_path));
513 
514     // Redirects |std::cin| to the file |stdin_path|. |in| is not freed because
515     // if it goes out of scope, |std::cin.rdbuf| will be freed, causing an
516     // error.
517     std::ifstream* in = new std::ifstream(stdin_path.value().c_str());
518     ASSERT_TRUE(in->is_open());
519     std::cin.rdbuf(in->rdbuf());
520 
521     pdf_file_save_path_ =
522         tmp_dir_.GetPath().Append(FILE_PATH_LITERAL("dummy.pdf"));
523 
524     // Send the file path to the layout test framework so that it can
525     // communicate with this browser test.
526     std::cout << "StdinPath: " << stdin_path.value() << "\n";
527     std::cout << "#EOF\n";
528     std::cout.flush();
529   }
530 
531  private:
532   // Generates a png from bitmap data and stores it in |png_output_|.
CreatePng(const std::vector<uint8_t> & bitmap_data,int width,int height)533   void CreatePng(const std::vector<uint8_t>& bitmap_data,
534                  int width,
535                  int height) {
536     base::MD5Sum(static_cast<const void*>(bitmap_data.data()),
537                  bitmap_data.size(),
538                  &hash_);
539     gfx::Rect png_rect(width, height);
540 
541     // tEXtchecksum looks funny, but that's what the layout test framework
542     // expects.
543     std::string comment_title("tEXtchecksum\x00");
544     gfx::PNGCodec::Comment hash_comment(comment_title,
545                                         base::MD5DigestToBase16(hash_));
546     std::vector<gfx::PNGCodec::Comment> comments;
547     comments.push_back(hash_comment);
548     ASSERT_TRUE(gfx::PNGCodec::Encode(bitmap_data.data(),
549                                       gfx::PNGCodec::FORMAT_BGRA,
550                                       png_rect.size(),
551                                       png_rect.size().width() * kColorChannels,
552                                       false,
553                                       comments,
554                                       &png_output_));
555   }
556 
557   std::unique_ptr<PrintPreviewObserver> print_preview_observer_;
558   base::FilePath pdf_file_save_path_;
559 
560   // Vector for storing the PNG to be sent to the layout test framework.
561   // TODO(ivandavid): Eventually change this to uint32_t and make everything
562   // work with that. It might be a bit tricky to fix everything to work with
563   // uint32_t, but not too tricky.
564   std::vector<unsigned char> png_output_;
565 
566   // Image hash of the bitmap that is turned into a PNG. The hash is put into
567   // the PNG as a comment, as it is needed by the layout test framework.
568   base::MD5Digest hash_;
569 
570   // Temporary directory for storing the pdf and the file for stdin. It is
571   // deleted by the layout tests.
572   // TODO(ivandavid): Keep it as a ScopedTempDir and change the layout test
573   // framework so that it tells the browser test how many test files there are.
574   base::ScopedTempDir tmp_dir_;
575 
576   DISALLOW_COPY_AND_ASSIGN(PrintPreviewPdfGeneratedBrowserTest);
577 };
578 
579 // This test acts as a driver for the layout test framework.
IN_PROC_BROWSER_TEST_F(PrintPreviewPdfGeneratedBrowserTest,MANUAL_LayoutTestDriver)580 IN_PROC_BROWSER_TEST_F(PrintPreviewPdfGeneratedBrowserTest,
581                        MANUAL_LayoutTestDriver) {
582   // What this code is supposed to do:
583   // - Setup communication with the layout test framework
584   // - Print webpage to a pdf
585   // - Convert pdf to a png
586   // - Send png to layout test framework, where it doesn an image diff
587   //   on the image sent by this test and a reference image.
588   //
589   // Throughout this code, there will be |std::cout| statements. The layout test
590   // framework uses stdout to get data from the browser test and uses stdin
591   // to send data to the browser test. Writing "EOF\n" to |std::cout| indicates
592   // that whatever block of data that the test was expecting has been completely
593   // sent. Sometimes EOF is printed to stderr because the test will expect it
594   // from stderr in addition to stdout for certain blocks of data.=
595   SetupStdinAndSavePath();
596 
597   while (true) {
598     std::string input;
599     while (input.empty()) {
600       std::getline(std::cin, input);
601       if (std::cin.eof())
602         std::cin.clear();
603     }
604 
605     // If the layout test framework sends "QUIT" to this test, that means there
606     // are no more tests for this instance to run and it should quit.
607     if (input == "QUIT")
608       break;
609 
610     base::FilePath::StringType file_extension = FILE_PATH_LITERAL(".pdf");
611     base::FilePath::StringType cmd;
612 #if defined(OS_POSIX)
613     cmd = input;
614 #elif defined(OS_WIN)
615     cmd = base::UTF8ToWide(input);
616 #endif
617 
618     DuplicateTab();
619     PrintPreviewSettings settings(
620         true, "", false, false, mojom::MarginType::kDefaultMargins,
621         cmd.find(file_extension) != base::FilePath::StringType::npos);
622 
623     // Splits the command sent by the layout test framework. The first command
624     // is always the file path to use for the test. The rest isn't relevant,
625     // so it can be ignored. The separator for the commands is an apostrophe.
626     std::vector<base::FilePath::StringType> cmd_arguments = base::SplitString(
627         cmd, base::FilePath::StringType(1, '\''),
628         base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
629 
630     ASSERT_GE(cmd_arguments.size(), 1U);
631     base::FilePath::StringType test_name(cmd_arguments[0]);
632     NavigateAndPrint(test_name, settings);
633     PdfToPng();
634 
635     // Message to the layout test framework indicating that it should start
636     // waiting for the image data, as there is no more text data to be read.
637     // There actually isn't any text data at all, however because the layout
638     // test framework requires it, a message has to be sent to stop it from
639     // waiting for this message and start waiting for the image data.
640     std::cout << "#EOF\n";
641     std::cout.flush();
642 
643     SendPng();
644     Reset();
645   }
646 }
647 
648 }  // namespace printing
649