Home | History | Annotate | Download | only in printing
      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_enumerator.h"
      8 #include "base/files/file_path.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 
     33 namespace {
     34 
     35 using printing::Image;
     36 
     37 const char kGenerateSwitch[] = "print-layout-generate";
     38 
     39 class 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) << 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) << 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(file_util::DieFileDie(emf_path_, true));
    176     EXPECT_TRUE(file_util::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(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(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 
    248 class 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.
    260 HWND 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.
    280 bool 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.
    296 class 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.
    333 IN_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 
    352 struct TestPool {
    353   const char* source;
    354   const wchar_t* result;
    355 };
    356 
    357 const 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
    367 IN_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.
    416 IN_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
    445 IN_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