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 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) << 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 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