blob: 3a11937679f2c1a0f674184db32884716266cbbb [file] [log] [blame]
[email protected]bc73b4e52010-03-26 04:16:201// Copyright (c) 2010 The Chromium Authors. All rights reserved.
[email protected]f7817822009-09-24 05:11:582// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome_frame/test/chrome_frame_test_utils.h"
6
7#include <atlbase.h>
8#include <atlwin.h>
9#include <iepmapi.h>
[email protected]42d7c022009-11-20 22:44:5810#include <sddl.h>
[email protected]f7817822009-09-24 05:11:5811
[email protected]5d91c9e2010-07-28 17:25:2812#include "base/command_line.h"
[email protected]59aa617b2010-09-02 22:02:0913#include "base/file_path.h"
[email protected]d8b83b212010-04-07 18:27:5214#include "base/file_version_info.h"
[email protected]d23ca372010-02-16 20:36:0915#include "base/path_service.h"
[email protected]d9f92192010-06-23 14:51:3216#include "base/process_util.h"
[email protected]f7817822009-09-24 05:11:5817#include "base/registry.h" // to find IE and firefox
[email protected]f7817822009-09-24 05:11:5818#include "base/scoped_handle.h"
[email protected]59aa617b2010-09-02 22:02:0919#include "base/scoped_ptr.h"
[email protected]5d91c9e2010-07-28 17:25:2820#include "base/string_util.h"
[email protected]d55194ca2010-03-11 18:25:4521#include "base/utf_string_conversions.h"
[email protected]f7817822009-09-24 05:11:5822#include "base/win_util.h"
23#include "chrome/common/chrome_switches.h"
[email protected]bc73b4e52010-03-26 04:16:2024#include "chrome/common/chrome_paths.h"
25#include "chrome/common/chrome_paths_internal.h"
[email protected]7bc272f2009-12-09 01:09:2826#include "chrome_frame/utils.h"
[email protected]bc73b4e52010-03-26 04:16:2027
[email protected]f7817822009-09-24 05:11:5828namespace chrome_frame_test {
29
[email protected]7aa8d972010-02-17 04:24:4930const int kDefaultWaitForIEToTerminateMs = 10 * 1000;
31
[email protected]f7817822009-09-24 05:11:5832const wchar_t kIEImageName[] = L"iexplore.exe";
33const wchar_t kIEBrokerImageName[] = L"ieuser.exe";
34const wchar_t kFirefoxImageName[] = L"firefox.exe";
35const wchar_t kOperaImageName[] = L"opera.exe";
36const wchar_t kSafariImageName[] = L"safari.exe";
[email protected]bcd840c2010-05-27 10:43:0037const char kChromeImageName[] = "chrome.exe";
[email protected]d8b83b212010-04-07 18:27:5238const wchar_t kIEProfileName[] = L"iexplore";
[email protected]128f9d92010-04-12 15:58:5239const wchar_t kChromeLauncher[] = L"chrome_launcher.exe";
[email protected]ba443b72010-05-14 23:34:3840const int kChromeFrameLongNavigationTimeoutInSeconds = 10;
[email protected]f7817822009-09-24 05:11:5841
[email protected]f7817822009-09-24 05:11:5842// Callback function for EnumThreadWindows.
43BOOL CALLBACK CloseWindowsThreadCallback(HWND hwnd, LPARAM param) {
44 int& count = *reinterpret_cast<int*>(param);
45 if (IsWindowVisible(hwnd)) {
46 if (IsWindowEnabled(hwnd)) {
47 DWORD results = 0;
48 if (!::SendMessageTimeout(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0, SMTO_BLOCK,
49 10000, &results)) {
[email protected]421e6142010-01-15 22:14:2550 LOG(WARNING) << "Window hung: " << StringPrintf(L"%08X", hwnd);
[email protected]f7817822009-09-24 05:11:5851 }
52 count++;
53 } else {
54 DLOG(WARNING) << "Skipping disabled window: "
55 << StringPrintf(L"%08X", hwnd);
56 }
57 }
58 return TRUE; // continue enumeration
59}
60
61// Attempts to close all non-child, visible windows on the given thread.
62// The return value is the number of visible windows a close request was
63// sent to.
64int CloseVisibleTopLevelWindowsOnThread(DWORD thread_id) {
65 int window_close_attempts = 0;
66 EnumThreadWindows(thread_id, CloseWindowsThreadCallback,
67 reinterpret_cast<LPARAM>(&window_close_attempts));
68 return window_close_attempts;
69}
70
71// Enumerates the threads of a process and attempts to close visible non-child
72// windows on all threads of the process.
73// The return value is the number of visible windows a close request was
74// sent to.
75int CloseVisibleWindowsOnAllThreads(HANDLE process) {
76 DWORD process_id = ::GetProcessId(process);
77 if (process_id == 0) {
78 NOTREACHED();
79 return 0;
80 }
81
82 ScopedHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0));
83 if (!snapshot.IsValid()) {
84 NOTREACHED();
85 return 0;
86 }
87
88 int window_close_attempts = 0;
89 THREADENTRY32 te = { sizeof(THREADENTRY32) };
90 if (Thread32First(snapshot, &te)) {
91 do {
92 if (RTL_CONTAINS_FIELD(&te, te.dwSize, th32OwnerProcessID) &&
93 te.th32OwnerProcessID == process_id) {
94 window_close_attempts +=
95 CloseVisibleTopLevelWindowsOnThread(te.th32ThreadID);
96 }
97 te.dwSize = sizeof(te);
98 } while (Thread32Next(snapshot, &te));
99 }
100
101 return window_close_attempts;
102}
103
[email protected]f7817822009-09-24 05:11:58104std::wstring GetExecutableAppPath(const std::wstring& file) {
105 std::wstring kAppPathsKey =
106 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\";
107
108 std::wstring app_path;
[email protected]87e700622010-08-14 02:13:43109 RegKey key(HKEY_LOCAL_MACHINE, (kAppPathsKey + file).c_str(), KEY_READ);
[email protected]f7817822009-09-24 05:11:58110 if (key.Handle()) {
111 key.ReadValue(NULL, &app_path);
112 }
113
114 return app_path;
115}
116
117std::wstring FormatCommandForApp(const std::wstring& exe_name,
118 const std::wstring& argument) {
119 std::wstring reg_path(StringPrintf(L"Applications\\%ls\\shell\\open\\command",
120 exe_name.c_str()));
[email protected]87e700622010-08-14 02:13:43121 RegKey key(HKEY_CLASSES_ROOT, reg_path.c_str(), KEY_READ);
[email protected]f7817822009-09-24 05:11:58122
123 std::wstring command;
124 if (key.Handle()) {
125 key.ReadValue(NULL, &command);
126 int found = command.find(L"%1");
127 if (found >= 0) {
128 command.replace(found, 2, argument);
129 }
130 }
131 return command;
132}
133
134base::ProcessHandle LaunchExecutable(const std::wstring& executable,
135 const std::wstring& argument) {
136 base::ProcessHandle process = NULL;
137 std::wstring path = GetExecutableAppPath(executable);
138 if (path.empty()) {
139 path = FormatCommandForApp(executable, argument);
140 if (path.empty()) {
[email protected]7ec1f4e2010-05-19 18:43:21141 LOG(ERROR) << "Failed to find executable: " << executable;
[email protected]f7817822009-09-24 05:11:58142 } else {
[email protected]51343d5a2009-10-26 22:39:33143 CommandLine cmdline = CommandLine::FromString(path);
[email protected]7ec1f4e2010-05-19 18:43:21144 if (!base::LaunchApp(cmdline, false, false, &process)) {
145 LOG(ERROR) << "LaunchApp failed: " << ::GetLastError();
146 }
[email protected]f7817822009-09-24 05:11:58147 }
148 } else {
[email protected]d1540c82009-10-27 00:18:32149 CommandLine cmdline((FilePath(path)));
[email protected]0445eb42010-08-13 22:10:30150 cmdline.AppendArgNative(argument);
[email protected]7ec1f4e2010-05-19 18:43:21151 if (!base::LaunchApp(cmdline, false, false, &process)) {
152 LOG(ERROR) << "LaunchApp failed: " << ::GetLastError();
153 }
[email protected]f7817822009-09-24 05:11:58154 }
155 return process;
156}
157
158base::ProcessHandle LaunchFirefox(const std::wstring& url) {
159 return LaunchExecutable(kFirefoxImageName, url);
160}
161
162base::ProcessHandle LaunchSafari(const std::wstring& url) {
163 return LaunchExecutable(kSafariImageName, url);
164}
165
166base::ProcessHandle LaunchChrome(const std::wstring& url) {
[email protected]bcd840c2010-05-27 10:43:00167 FilePath path;
[email protected]d23ca372010-02-16 20:36:09168 PathService::Get(base::DIR_MODULE, &path);
[email protected]bcd840c2010-05-27 10:43:00169 path = path.AppendASCII(kChromeImageName);
[email protected]d23ca372010-02-16 20:36:09170
[email protected]bcd840c2010-05-27 10:43:00171 CommandLine cmd(path);
[email protected]0445eb42010-08-13 22:10:30172 cmd.AppendSwitch(switches::kNoFirstRun);
173 cmd.AppendArgNative(url);
[email protected]d23ca372010-02-16 20:36:09174
175 base::ProcessHandle process = NULL;
176 base::LaunchApp(cmd, false, false, &process);
177 return process;
[email protected]f7817822009-09-24 05:11:58178}
179
180base::ProcessHandle LaunchOpera(const std::wstring& url) {
181 // NOTE: For Opera tests to work it must be configured to start up with
182 // a blank page. There is an command line switch, -nosession, that's supposed
183 // to avoid opening up the previous session, but that switch is not working.
184 // TODO(tommi): Include a special ini file (opera6.ini) for opera and launch
185 // with our required settings. This file is by default stored here:
186 // "%USERPROFILE%\Application Data\Opera\Opera\profile\opera6.ini"
187 return LaunchExecutable(kOperaImageName, url);
188}
189
190base::ProcessHandle LaunchIEOnVista(const std::wstring& url) {
191 typedef HRESULT (WINAPI* IELaunchURLPtr)(
192 const wchar_t* url,
193 PROCESS_INFORMATION *pi,
194 VOID *info);
195
196 IELaunchURLPtr launch;
197 PROCESS_INFORMATION pi = {0};
198 IELAUNCHURLINFO info = {sizeof info, 0};
199 HMODULE h = LoadLibrary(L"ieframe.dll");
[email protected]7ec1f4e2010-05-19 18:43:21200 if (!h) {
201 LOG(ERROR) << "Failed to load ieframe.dll: " << ::GetLastError();
[email protected]f7817822009-09-24 05:11:58202 return NULL;
[email protected]7ec1f4e2010-05-19 18:43:21203 }
[email protected]f7817822009-09-24 05:11:58204 launch = reinterpret_cast<IELaunchURLPtr>(GetProcAddress(h, "IELaunchURL"));
[email protected]7ec1f4e2010-05-19 18:43:21205 CHECK(launch);
[email protected]f7817822009-09-24 05:11:58206 HRESULT hr = launch(url.c_str(), &pi, &info);
207 FreeLibrary(h);
[email protected]7ec1f4e2010-05-19 18:43:21208 if (SUCCEEDED(hr)) {
[email protected]f7817822009-09-24 05:11:58209 CloseHandle(pi.hThread);
[email protected]7ec1f4e2010-05-19 18:43:21210 } else {
211 LOG(ERROR) << ::StringPrintf("IELaunchURL failed: 0x%08X", hr);
212 }
[email protected]f7817822009-09-24 05:11:58213 return pi.hProcess;
214}
215
216base::ProcessHandle LaunchIE(const std::wstring& url) {
217 if (win_util::GetWinVersion() >= win_util::WINVERSION_VISTA) {
218 return LaunchIEOnVista(url);
219 } else {
220 return LaunchExecutable(kIEImageName, url);
221 }
222}
223
224int CloseAllIEWindows() {
225 int ret = 0;
226
227 ScopedComPtr<IShellWindows> windows;
228 HRESULT hr = ::CoCreateInstance(__uuidof(ShellWindows), NULL, CLSCTX_ALL,
229 IID_IShellWindows, reinterpret_cast<void**>(windows.Receive()));
230 DCHECK(SUCCEEDED(hr));
231
232 if (SUCCEEDED(hr)) {
233 long count = 0; // NOLINT
234 windows->get_Count(&count);
235 VARIANT i = { VT_I4 };
236 for (i.lVal = 0; i.lVal < count; ++i.lVal) {
237 ScopedComPtr<IDispatch> folder;
238 windows->Item(i, folder.Receive());
239 if (folder != NULL) {
240 ScopedComPtr<IWebBrowser2> browser;
241 if (SUCCEEDED(browser.QueryFrom(folder))) {
[email protected]59aa617b2010-09-02 22:02:09242 bool is_ie = true;
243 HWND window = NULL;
244 // Check the class of the browser window to make sure we only close
245 // IE windows.
246 if (browser->get_HWND(reinterpret_cast<SHANDLE_PTR*>(window))) {
247 wchar_t class_name[MAX_PATH];
248 if (::GetClassName(window, class_name, arraysize(class_name))) {
249 is_ie = _wcsicmp(class_name, L"IEFrame") == 0;
250 }
251 }
252 if (is_ie) {
253 browser->Quit();
254 ++ret;
255 }
[email protected]f7817822009-09-24 05:11:58256 }
257 }
258 }
259 }
260
261 return ret;
262}
263
[email protected]42d7c022009-11-20 22:44:58264
265LowIntegrityToken::LowIntegrityToken() : impersonated_(false) {
266}
267
268LowIntegrityToken::~LowIntegrityToken() {
269 RevertToSelf();
270}
271
272BOOL LowIntegrityToken::RevertToSelf() {
273 BOOL ok = TRUE;
274 if (impersonated_) {
275 DCHECK(IsImpersonated());
276 ok = ::RevertToSelf();
277 if (ok)
278 impersonated_ = false;
279 }
280
281 return ok;
282}
283
284BOOL LowIntegrityToken::Impersonate() {
285 DCHECK(!impersonated_);
286 DCHECK(!IsImpersonated());
287 HANDLE process_token_handle = NULL;
288 BOOL ok = ::OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE,
289 &process_token_handle);
290 if (!ok) {
291 DLOG(ERROR) << "::OpenProcessToken failed: " << GetLastError();
292 return ok;
293 }
294
295 ScopedHandle process_token(process_token_handle);
296 // Create impersonation low integrity token.
297 HANDLE impersonation_token_handle = NULL;
298 ok = ::DuplicateTokenEx(process_token,
299 TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_ADJUST_DEFAULT, NULL,
300 SecurityImpersonation, TokenImpersonation, &impersonation_token_handle);
301 if (!ok) {
302 DLOG(ERROR) << "::DuplicateTokenEx failed: " << GetLastError();
303 return ok;
304 }
305
[email protected]7ec1f4e2010-05-19 18:43:21306 // TODO(stoyan): sandbox/src/restricted_token_utils.cc has
307 // SetTokenIntegrityLevel function already.
[email protected]42d7c022009-11-20 22:44:58308 ScopedHandle impersonation_token(impersonation_token_handle);
309 PSID integrity_sid = NULL;
310 TOKEN_MANDATORY_LABEL tml = {0};
311 ok = ::ConvertStringSidToSid(SDDL_ML_LOW, &integrity_sid);
312 if (!ok) {
313 DLOG(ERROR) << "::ConvertStringSidToSid failed: " << GetLastError();
314 return ok;
315 }
316
317 tml.Label.Attributes = SE_GROUP_INTEGRITY | SE_GROUP_INTEGRITY_ENABLED;
318 tml.Label.Sid = integrity_sid;
319 ok = ::SetTokenInformation(impersonation_token, TokenIntegrityLevel,
320 &tml, sizeof(tml) + ::GetLengthSid(integrity_sid));
321 ::LocalFree(integrity_sid);
322 if (!ok) {
323 DLOG(ERROR) << "::SetTokenInformation failed: " << GetLastError();
324 return ok;
325 }
326
327 // Switch current thread to low integrity.
328 ok = ::ImpersonateLoggedOnUser(impersonation_token);
329 if (ok) {
330 impersonated_ = true;
331 } else {
332 DLOG(ERROR) << "::ImpersonateLoggedOnUser failed: " << GetLastError();
333 }
334
335 return ok;
336}
337
338bool LowIntegrityToken::IsImpersonated() {
339 HANDLE token = NULL;
340 if (!::OpenThreadToken(::GetCurrentThread(), 0, false, &token) &&
341 ::GetLastError() != ERROR_NO_TOKEN) {
342 return true;
343 }
344
345 if (token)
346 ::CloseHandle(token);
347
348 return false;
349}
350
[email protected]7bc272f2009-12-09 01:09:28351HRESULT LaunchIEAsComServer(IWebBrowser2** web_browser) {
352 if (!web_browser)
353 return E_INVALIDARG;
354
[email protected]7aa8d972010-02-17 04:24:49355 AllowSetForegroundWindow(ASFW_ANY);
356
[email protected]7bc272f2009-12-09 01:09:28357 HRESULT hr = S_OK;
358 DWORD cocreate_flags = CLSCTX_LOCAL_SERVER;
359 chrome_frame_test::LowIntegrityToken token;
360 // Vista has a bug which manifests itself when a medium integrity process
361 // launches a COM server like IE which runs in protected mode due to UAC.
362 // This causes the IWebBrowser2 interface which is returned to be useless,
363 // i.e it does not receive any events, etc. Our workaround for this is
364 // to impersonate a low integrity token and then launch IE.
[email protected]6448b3ab2010-07-12 22:07:03365 if (win_util::GetWinVersion() == win_util::WINVERSION_VISTA &&
366 GetInstalledIEVersion() == IE_7) {
[email protected]7bc272f2009-12-09 01:09:28367 // Create medium integrity browser that will launch IE broker.
368 ScopedComPtr<IWebBrowser2> medium_integrity_browser;
369 hr = medium_integrity_browser.CreateInstance(CLSID_InternetExplorer, NULL,
370 CLSCTX_LOCAL_SERVER);
371 if (FAILED(hr))
372 return hr;
373 medium_integrity_browser->Quit();
374 // Broker remains alive.
375 if (!token.Impersonate()) {
376 hr = HRESULT_FROM_WIN32(GetLastError());
377 return hr;
378 }
379
380 cocreate_flags |= CLSCTX_ENABLE_CLOAKING;
381 }
382
383 hr = ::CoCreateInstance(CLSID_InternetExplorer, NULL,
384 cocreate_flags, IID_IWebBrowser2,
385 reinterpret_cast<void**>(web_browser));
386 // ~LowIntegrityToken() will switch integrity back to medium.
387 return hr;
388}
389
[email protected]bc73b4e52010-03-26 04:16:20390FilePath GetProfilePath(const std::wstring& profile_name) {
391 FilePath profile_path;
392 chrome::GetChromeFrameUserDataDirectory(&profile_path);
393 return profile_path.Append(profile_name);
394}
395
[email protected]1da35172010-04-22 02:30:03396std::wstring GetExeVersion(const std::wstring& exe_path) {
397 scoped_ptr<FileVersionInfo> ie_version_info(
398 FileVersionInfo::CreateFileVersionInfo(FilePath(exe_path)));
399 return ie_version_info->product_version();
400}
401
402IEVersion GetInstalledIEVersion() {
403 std::wstring path = chrome_frame_test::GetExecutableAppPath(kIEImageName);
404 std::wstring version = GetExeVersion(path);
405
406 switch (version[0]) {
407 case '6':
408 return IE_6;
409 case '7':
410 return IE_7;
411 case '8':
412 return IE_8;
413 default:
414 break;
415 }
416
417 return IE_UNSUPPORTED;
418}
419
[email protected]d8b83b212010-04-07 18:27:52420FilePath GetProfilePathForIE() {
421 FilePath profile_path;
422 // Browsers without IDeleteBrowsingHistory in non-priv mode
423 // have their profiles moved into "Temporary Internet Files".
424 // The code below basically retrieves the version of IE and computes
425 // the profile directory accordingly.
[email protected]1da35172010-04-22 02:30:03426 if (GetInstalledIEVersion() == IE_8) {
[email protected]d8b83b212010-04-07 18:27:52427 profile_path = GetProfilePath(kIEProfileName);
428 } else {
429 profile_path = GetIETemporaryFilesFolder();
430 profile_path = profile_path.Append(L"Google Chrome Frame");
431 }
432 return profile_path;
433}
434
[email protected]6448b3ab2010-07-12 22:07:03435FilePath GetTestDataFolder() {
436 FilePath test_dir;
437 PathService::Get(base::DIR_SOURCE_ROOT, &test_dir);
438 test_dir = test_dir.Append(FILE_PATH_LITERAL("chrome_frame"))
439 .Append(FILE_PATH_LITERAL("test"))
440 .Append(FILE_PATH_LITERAL("data"));
441 return test_dir;
442}
443
444std::wstring GetPathFromUrl(const std::wstring& url) {
445 string16 url16 = WideToUTF16(url);
446 GURL gurl = GURL(url16);
447 if (gurl.has_query()) {
448 GURL::Replacements replacements;
449 replacements.ClearQuery();
450 gurl = gurl.ReplaceComponents(replacements);
451 }
452 return UTF8ToWide(gurl.PathForRequest());
453}
454
455std::wstring GetPathAndQueryFromUrl(const std::wstring& url) {
456 string16 url16 = WideToUTF16(url);
457 GURL gurl = GURL(url16);
458 return UTF8ToWide(gurl.PathForRequest());
459}
460
461bool AddCFMetaTag(std::string* html_data) {
462 if (!html_data) {
463 NOTREACHED();
464 return false;
465 }
[email protected]59aa617b2010-09-02 22:02:09466 std::string lower = StringToLowerASCII(*html_data);
467 size_t head = lower.find("<head>");
[email protected]6448b3ab2010-07-12 22:07:03468 if (head == std::string::npos) {
469 // Add missing head section.
[email protected]59aa617b2010-09-02 22:02:09470 size_t html = lower.find("<html>");
[email protected]6448b3ab2010-07-12 22:07:03471 if (html != std::string::npos) {
[email protected]59aa617b2010-09-02 22:02:09472 head = html + strlen("<html>");
473 html_data->insert(head, "<head></head>");
474 } else {
475 DLOG(ERROR) << "Meta tag will not be injected "
476 << "because the html tag could not be found";
[email protected]6448b3ab2010-07-12 22:07:03477 }
478 }
479 if (head != std::string::npos) {
480 html_data->insert(
481 head + strlen("<head>"),
482 "<meta http-equiv=\"x-ua-compatible\" content=\"chrome=1\" />");
483 }
484 return head != std::string::npos;
485}
486
[email protected]5d91c9e2010-07-28 17:25:28487CloseIeAtEndOfScope::~CloseIeAtEndOfScope() {
488 int closed = CloseAllIEWindows();
489 DLOG_IF(ERROR, closed != 0) << "Closed " << closed << " windows forcefully";
490}
491
[email protected]d9f92192010-06-23 14:51:32492base::ProcessHandle StartCrashService() {
493 FilePath exe_dir;
494 if (!PathService::Get(base::DIR_EXE, &exe_dir)) {
495 DCHECK(false);
496 return NULL;
497 }
498
499 base::ProcessHandle crash_service = NULL;
500
501 FilePath crash_service_path = exe_dir.AppendASCII("crash_service.exe");
502 if (!base::LaunchApp(crash_service_path.value(), false, false,
503 &crash_service)) {
504 DLOG(ERROR) << "Couldn't start crash_service.exe";
505 return NULL;
506 }
507
508 DLOG(INFO) << "Started crash_service.exe so you know if a test crashes!";
509 // This sleep is to ensure that the crash service is done initializing, i.e
510 // the pipe creation, etc.
511 Sleep(500);
512 return crash_service;
513}
514
[email protected]f7817822009-09-24 05:11:58515} // namespace chrome_frame_test