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/files/file_enumerator.h"
7#include "base/files/file_path.h"
8#include "base/files/file_util.h"
9#include "base/message_loop/message_loop.h"
10#include "base/path_service.h"
11#include "base/process/process.h"
12#include "base/strings/string_util.h"
13#include "base/strings/utf_string_conversions.h"
14#include "base/test/test_file_util.h"
15#include "base/threading/simple_thread.h"
16#include "chrome/browser/chrome_notification_types.h"
17#include "chrome/browser/printing/print_job.h"
18#include "chrome/browser/printing/print_view_manager.h"
19#include "chrome/browser/ui/browser.h"
20#include "chrome/browser/ui/browser_commands.h"
21#include "chrome/browser/ui/tabs/tab_strip_model.h"
22#include "chrome/common/chrome_paths.h"
23#include "chrome/common/chrome_switches.h"
24#include "chrome/test/base/in_process_browser_test.h"
25#include "chrome/test/base/ui_test_utils.h"
26#include "content/public/browser/notification_observer.h"
27#include "content/public/browser/notification_registrar.h"
28#include "content/public/browser/notification_service.h"
29#include "net/test/spawned_test_server/spawned_test_server.h"
30#include "printing/image.h"
31#include "printing/printing_test.h"
32
33namespace {
34
35using printing::Image;
36
37const char kGenerateSwitch[] = "print-layout-generate";
38
39class PrintingLayoutTest : public PrintingTest<InProcessBrowserTest>,
40                           public content::NotificationObserver {
41 public:
42  PrintingLayoutTest() {
43    base::FilePath browser_directory;
44    PathService::Get(chrome::DIR_APP, &browser_directory);
45    emf_path_ = browser_directory.AppendASCII("metafile_dumps");
46  }
47
48  virtual void SetUp() OVERRIDE {
49    // Make sure there is no left overs.
50    CleanupDumpDirectory();
51    InProcessBrowserTest::SetUp();
52  }
53
54  virtual void TearDown() OVERRIDE {
55    InProcessBrowserTest::TearDown();
56    base::DeleteFile(emf_path_, true);
57  }
58
59  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
60    command_line->AppendSwitchPath(switches::kDebugPrint, emf_path_);
61  }
62
63 protected:
64  void PrintNowTab() {
65    registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
66                   content::NotificationService::AllSources());
67
68    content::WebContents* web_contents =
69        browser()->tab_strip_model()->GetActiveWebContents();
70    printing::PrintViewManager::FromWebContents(web_contents)->PrintNow();
71    content::RunMessageLoop();
72    registrar_.RemoveAll();
73  }
74
75  virtual void Observe(int type,
76                       const content::NotificationSource& source,
77                       const content::NotificationDetails& details) {
78    ASSERT_EQ(chrome::NOTIFICATION_PRINT_JOB_EVENT, type);
79    switch (content::Details<printing::JobEventDetails>(details)->type()) {
80      case printing::JobEventDetails::JOB_DONE: {
81        // Succeeded.
82        base::MessageLoop::current()->PostTask(
83            FROM_HERE, base::MessageLoop::QuitClosure());
84        break;
85      }
86      case printing::JobEventDetails::USER_INIT_CANCELED:
87      case printing::JobEventDetails::FAILED: {
88        // Failed.
89        ASSERT_TRUE(false);
90        base::MessageLoop::current()->PostTask(
91            FROM_HERE, base::MessageLoop::QuitClosure());
92        break;
93      }
94      case printing::JobEventDetails::NEW_DOC:
95      case printing::JobEventDetails::USER_INIT_DONE:
96      case printing::JobEventDetails::DEFAULT_INIT_DONE:
97      case printing::JobEventDetails::NEW_PAGE:
98      case printing::JobEventDetails::PAGE_DONE:
99      case printing::JobEventDetails::DOC_DONE:
100      case printing::JobEventDetails::ALL_PAGES_REQUESTED: {
101        // Don't care.
102        break;
103      }
104      default: {
105        NOTREACHED();
106        break;
107      }
108    }
109  }
110
111  // Finds the dump for the last print job and compares it to the data named
112  // |verification_name|. Compares the saved printed job pixels with the test
113  // data pixels and returns the percentage of different pixels; 0 for success,
114  // [0, 100] for failure.
115  double CompareWithResult(const std::wstring& verification_name) {
116    base::FilePath test_result(ScanFiles(verification_name));
117    if (test_result.value().empty()) {
118      // 100% different, the print job buffer is not there.
119      return 100.;
120    }
121
122    base::FilePath base_path(ui_test_utils::GetTestFilePath(
123        base::FilePath().AppendASCII("printing"), base::FilePath()));
124    base::FilePath emf(base_path.Append(verification_name + L".emf"));
125    base::FilePath png(base_path.Append(verification_name + L".png"));
126
127    base::FilePath cleartype(
128        base_path.Append(verification_name + L"_cleartype.png"));
129    // Looks for Cleartype override.
130    if (base::PathExists(cleartype) && IsClearTypeEnabled())
131      png = cleartype;
132
133    if (GenerateFiles()) {
134      // Copy the .emf and generate an .png.
135      base::CopyFile(test_result, emf);
136      Image emf_content(emf);
137      emf_content.SaveToPng(png);
138      // Saving is always fine.
139      return 0;
140    } else {
141      // File compare between test and result.
142      Image emf_content(emf);
143      Image test_content(test_result);
144      Image png_content(png);
145      double diff_emf = emf_content.PercentageDifferent(test_content);
146
147      EXPECT_EQ(0., diff_emf) << base::WideToUTF8(verification_name) <<
148          " original size:" << emf_content.size().ToString() <<
149          " result size:" << test_content.size().ToString();
150      if (diff_emf) {
151        // Backup the result emf file.
152        base::FilePath failed(
153            base_path.Append(verification_name + L"_failed.emf"));
154        base::CopyFile(test_result, failed);
155      }
156
157      // This verification is only to know that the EMF rendering stays
158      // immutable.
159      double diff_png = emf_content.PercentageDifferent(png_content);
160      EXPECT_EQ(0., diff_png) << base::WideToUTF8(verification_name) <<
161          " original size:" << emf_content.size().ToString() <<
162          " result size:" << test_content.size().ToString();
163      if (diff_png) {
164        // Backup the rendered emf file to detect the rendering difference.
165        base::FilePath rendering(
166            base_path.Append(verification_name + L"_rendering.png"));
167        emf_content.SaveToPng(rendering);
168      }
169      return std::max(diff_png, diff_emf);
170    }
171  }
172
173  // Makes sure the directory exists and is empty.
174  void CleanupDumpDirectory() {
175    EXPECT_TRUE(base::DieFileDie(emf_path_, true));
176    EXPECT_TRUE(base::CreateDirectory(emf_path_));
177  }
178
179  // Returns if Clear Type is currently enabled.
180  static bool IsClearTypeEnabled() {
181    BOOL ct_enabled = 0;
182    if (SystemParametersInfo(SPI_GETCLEARTYPE, 0, &ct_enabled, 0) && ct_enabled)
183      return true;
184    UINT smoothing = 0;
185    if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &smoothing, 0) &&
186        smoothing == FE_FONTSMOOTHINGCLEARTYPE)
187      return true;
188    return false;
189  }
190
191 private:
192  // Verifies that there is one .emf and one .prn file in the dump directory.
193  // Returns the path of the .emf file and deletes the .prn file.
194  std::wstring ScanFiles(const std::wstring& verification_name) {
195    // Try to 10 seconds.
196    std::wstring emf_file;
197    std::wstring prn_file;
198    bool found_emf = false;
199    bool found_prn = false;
200    for (int i = 0; i < 100; ++i) {
201      base::FileEnumerator enumerator(emf_path_, false,
202                                      base::FileEnumerator::FILES);
203      emf_file.clear();
204      prn_file.clear();
205      found_emf = false;
206      found_prn = false;
207      base::FilePath file;
208      while (!(file = enumerator.Next()).empty()) {
209        std::wstring ext = file.Extension();
210        if (base::strcasecmp(base::WideToUTF8(ext).c_str(), ".emf") == 0) {
211          EXPECT_FALSE(found_emf) << "Found a leftover .EMF file: \"" <<
212              emf_file << "\" and \"" << file.value() <<
213              "\" when looking for \"" << verification_name << "\"";
214          found_emf = true;
215          emf_file = file.value();
216          continue;
217        }
218        if (base::strcasecmp(base::WideToUTF8(ext).c_str(), ".prn") == 0) {
219          EXPECT_FALSE(found_prn) << "Found a leftover .PRN file: \"" <<
220              prn_file << "\" and \"" << file.value() <<
221              "\" when looking for \"" << verification_name << "\"";
222          prn_file = file.value();
223          found_prn = true;
224          base::DeleteFile(file, false);
225          continue;
226        }
227        EXPECT_TRUE(false);
228      }
229      if (found_emf && found_prn)
230        break;
231      base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
232    }
233    EXPECT_TRUE(found_emf) << ".PRN file is: " << prn_file;
234    EXPECT_TRUE(found_prn) << ".EMF file is: " << emf_file;
235    return emf_file;
236  }
237
238  static bool GenerateFiles() {
239    return CommandLine::ForCurrentProcess()->HasSwitch(kGenerateSwitch);
240  }
241
242  base::FilePath emf_path_;
243  content::NotificationRegistrar registrar_;
244
245  DISALLOW_COPY_AND_ASSIGN(PrintingLayoutTest);
246};
247
248class PrintingLayoutTextTest : public PrintingLayoutTest {
249  typedef PrintingLayoutTest Parent;
250 public:
251  // Returns if the test is disabled.
252  // http://crbug.com/64869 Until the issue is fixed, disable the test if
253  // ClearType is enabled.
254  static bool IsTestCaseDisabled() {
255    return Parent::IsTestCaseDisabled() || IsClearTypeEnabled();
256  }
257};
258
259// Finds the first dialog window owned by owner_process.
260HWND FindDialogWindow(DWORD owner_process) {
261  HWND dialog_window(NULL);
262  for (;;) {
263    dialog_window = FindWindowEx(NULL,
264                                 dialog_window,
265                                 MAKEINTATOM(32770),
266                                 NULL);
267    if (!dialog_window)
268      break;
269
270    // The dialog must be owned by our target process.
271    DWORD process_id = 0;
272    GetWindowThreadProcessId(dialog_window, &process_id);
273    if (process_id == owner_process)
274      break;
275  }
276  return dialog_window;
277}
278
279// Tries to close a dialog window.
280bool CloseDialogWindow(HWND dialog_window) {
281  LRESULT res = SendMessage(dialog_window, DM_GETDEFID, 0, 0);
282  if (!res)
283    return false;
284  EXPECT_EQ(DC_HASDEFID, HIWORD(res));
285  WORD print_button_id = LOWORD(res);
286  res = SendMessage(
287      dialog_window,
288      WM_COMMAND,
289      print_button_id,
290      reinterpret_cast<LPARAM>(GetDlgItem(dialog_window, print_button_id)));
291  return res == 0;
292}
293
294// Dismiss the first dialog box owned by owner_process by "executing" the
295// default button.
296class DismissTheWindow : public base::DelegateSimpleThread::Delegate {
297 public:
298  DismissTheWindow()
299      : owner_process_(base::Process::Current().pid()) {
300  }
301
302  virtual void Run() {
303    HWND dialog_window;
304    for (;;) {
305      // First enumerate the windows.
306      dialog_window = FindDialogWindow(owner_process_);
307
308      // Try to close it.
309      if (dialog_window) {
310        if (CloseDialogWindow(dialog_window)) {
311          break;
312        }
313      }
314      base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
315    }
316
317    // Now verify that it indeed closed itself.
318    while (IsWindow(dialog_window)) {
319      CloseDialogWindow(dialog_window);
320      base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
321    }
322  }
323
324  DWORD owner_process() { return owner_process_; }
325
326 private:
327  DWORD owner_process_;
328};
329
330}  // namespace
331
332// Fails, see http://crbug.com/7721.
333IN_PROC_BROWSER_TEST_F(PrintingLayoutTextTest, DISABLED_Complex) {
334  if (IsTestCaseDisabled())
335    return;
336
337  DismissTheWindow dismisser;
338  base::DelegateSimpleThread close_printdlg_thread(&dismisser,
339                                                   "close_printdlg_thread");
340
341  // Print a document, check its output.
342  ASSERT_TRUE(test_server()->Start());
343
344  ui_test_utils::NavigateToURL(
345      browser(), test_server()->GetURL("files/printing/test1.html"));
346  close_printdlg_thread.Start();
347  PrintNowTab();
348  close_printdlg_thread.Join();
349  EXPECT_EQ(0., CompareWithResult(L"test1"));
350}
351
352struct TestPool {
353  const char* source;
354  const wchar_t* result;
355};
356
357const TestPool kTestPool[] = {
358  // ImagesB&W
359  "files/printing/test2.html", L"test2",
360  // ImagesTransparent
361  "files/printing/test3.html", L"test3",
362  // ImageColor
363  "files/printing/test4.html", L"test4",
364};
365
366// http://crbug.com/7721
367IN_PROC_BROWSER_TEST_F(PrintingLayoutTest, DISABLED_ManyTimes) {
368  if (IsTestCaseDisabled())
369    return;
370
371  ASSERT_TRUE(test_server()->Start());
372
373  DismissTheWindow dismisser;
374
375  ASSERT_GT(arraysize(kTestPool), 0u);
376  for (int i = 0; i < arraysize(kTestPool); ++i) {
377    if (i)
378      CleanupDumpDirectory();
379    const TestPool& test = kTestPool[i % arraysize(kTestPool)];
380    ui_test_utils::NavigateToURL(browser(), test_server()->GetURL(test.source));
381    base::DelegateSimpleThread close_printdlg_thread1(&dismisser,
382                                                      "close_printdlg_thread");
383    EXPECT_EQ(NULL, FindDialogWindow(dismisser.owner_process()));
384    close_printdlg_thread1.Start();
385    PrintNowTab();
386    close_printdlg_thread1.Join();
387    EXPECT_EQ(0., CompareWithResult(test.result)) << test.result;
388    CleanupDumpDirectory();
389    base::DelegateSimpleThread close_printdlg_thread2(&dismisser,
390                                                      "close_printdlg_thread");
391    EXPECT_EQ(NULL, FindDialogWindow(dismisser.owner_process()));
392    close_printdlg_thread2.Start();
393    PrintNowTab();
394    close_printdlg_thread2.Join();
395    EXPECT_EQ(0., CompareWithResult(test.result)) << test.result;
396    CleanupDumpDirectory();
397    base::DelegateSimpleThread close_printdlg_thread3(&dismisser,
398                                                      "close_printdlg_thread");
399    EXPECT_EQ(NULL, FindDialogWindow(dismisser.owner_process()));
400    close_printdlg_thread3.Start();
401    PrintNowTab();
402    close_printdlg_thread3.Join();
403    EXPECT_EQ(0., CompareWithResult(test.result)) << test.result;
404    CleanupDumpDirectory();
405    base::DelegateSimpleThread close_printdlg_thread4(&dismisser,
406                                                      "close_printdlg_thread");
407    EXPECT_EQ(NULL, FindDialogWindow(dismisser.owner_process()));
408    close_printdlg_thread4.Start();
409    PrintNowTab();
410    close_printdlg_thread4.Join();
411    EXPECT_EQ(0., CompareWithResult(test.result)) << test.result;
412  }
413}
414
415// Prints a popup and immediately closes it. Disabled because it crashes.
416IN_PROC_BROWSER_TEST_F(PrintingLayoutTest, DISABLED_Delayed) {
417  if (IsTestCaseDisabled())
418    return;
419
420  ASSERT_TRUE(test_server()->Start());
421
422  {
423    bool is_timeout = true;
424    GURL url = test_server()->GetURL("files/printing/popup_delayed_print.htm");
425    ui_test_utils::NavigateToURL(browser(), url);
426
427    DismissTheWindow dismisser;
428    base::DelegateSimpleThread close_printdlg_thread(&dismisser,
429                                                     "close_printdlg_thread");
430    close_printdlg_thread.Start();
431    close_printdlg_thread.Join();
432
433    // Force a navigation elsewhere to verify that it's fine with it.
434    url = test_server()->GetURL("files/printing/test1.html");
435    ui_test_utils::NavigateToURL(browser(), url);
436  }
437  chrome::CloseWindow(browser());
438  content::RunAllPendingInMessageLoop();
439
440  EXPECT_EQ(0., CompareWithResult(L"popup_delayed_print"))
441      << L"popup_delayed_print";
442}
443
444// Prints a popup and immediately closes it. http://crbug.com/7721
445IN_PROC_BROWSER_TEST_F(PrintingLayoutTest, DISABLED_IFrame) {
446  if (IsTestCaseDisabled())
447    return;
448
449  ASSERT_TRUE(test_server()->Start());
450
451  {
452    GURL url = test_server()->GetURL("files/printing/iframe.htm");
453    ui_test_utils::NavigateToURL(browser(), url);
454
455    DismissTheWindow dismisser;
456    base::DelegateSimpleThread close_printdlg_thread(&dismisser,
457                                                     "close_printdlg_thread");
458    close_printdlg_thread.Start();
459    close_printdlg_thread.Join();
460
461    // Force a navigation elsewhere to verify that it's fine with it.
462    url = test_server()->GetURL("files/printing/test1.html");
463    ui_test_utils::NavigateToURL(browser(), url);
464  }
465  chrome::CloseWindow(browser());
466  content::RunAllPendingInMessageLoop();
467
468  EXPECT_EQ(0., CompareWithResult(L"iframe")) << L"iframe";
469}
470