Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/cpp/iedriver/BrowserFactory.cpp
2867 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
17
#include "BrowserFactory.h"
18
19
#include <ctime>
20
#include <vector>
21
22
#include <exdispid.h>
23
#include <iepmapi.h>
24
#include <psapi.h>
25
#include <sddl.h>
26
#include <shlguid.h>
27
#include <shlobj.h>
28
29
#include "logging.h"
30
31
#include "FileUtilities.h"
32
#include "RegistryUtilities.h"
33
#include "StringUtilities.h"
34
#include "WebDriverConstants.h"
35
36
#define HTML_GETOBJECT_MSG L"WM_HTML_GETOBJECT"
37
#define OLEACC_LIBRARY_NAME L"OLEACC.DLL"
38
#define IEFRAME_LIBRARY_NAME L"ieframe.dll"
39
#define IELAUNCHURL_FUNCTION_NAME "IELaunchURL"
40
41
#define IE_FRAME_WINDOW_CLASS "IEFrame"
42
#define SHELL_DOCOBJECT_VIEW_WINDOW_CLASS "Shell DocObject View"
43
#define IE_SERVER_CHILD_WINDOW_CLASS "Internet Explorer_Server"
44
#define ANDIE_FRAME_WINDOW_CLASS "Chrome_WidgetWin_1"
45
46
#define EDGE_REGISTRY_KEY L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\msedge.exe"
47
#define IE_CLSID_REGISTRY_KEY L"SOFTWARE\\Classes\\InternetExplorer.Application\\CLSID"
48
#define IE_REDIRECT L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Ext\\CLSID"
49
#define IE_SECURITY_ZONES_REGISTRY_KEY L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Zones"
50
#define IE_TABPROCGROWTH_REGISTRY_KEY L"Software\\Microsoft\\Internet Explorer\\Main"
51
52
#define IE_PROTECTED_MODE_SETTING_VALUE_NAME L"2500"
53
54
#define IELAUNCHURL_ERROR_MESSAGE "IELaunchURL() returned HRESULT %X ('%s') for URL '%s'"
55
#define CREATEPROCESS_ERROR_MESSAGE "CreateProcess() failed for command line '%s'"
56
#define CREATEPROCESS_EDGE_ERROR "CreateProcess() failed for edge with the following command: "
57
#define NULL_PROCESS_ID_ERROR_MESSAGE " successfully launched Internet Explorer, but did not return a valid process ID."
58
#define PROTECTED_MODE_SETTING_ERROR_MESSAGE "Protected Mode settings are not the same for all zones. Enable Protected Mode must be set to the same value (enabled or disabled) for all zones."
59
#define ZOOM_SETTING_ERROR_MESSAGE "Browser zoom level was set to %d%%. It should be set to 100%%"
60
#define ATTACH_TIMEOUT_ERROR_MESSAGE "Could not find an Internet Explorer window belonging to the process with ID %d within %d milliseconds."
61
#define ATTACH_FAILURE_ERROR_MESSAGE "Found browser window using ShellWindows API, but could not attach to the browser IWebBrowser2 object."
62
#define CREATEPROCESS_REGISTRY_ERROR_MESSAGE "Unable to use CreateProcess() API. To use CreateProcess() with Internet Explorer 8 or higher, the value of registry setting in HKEY_CURRENT_USER\\Software\\Microsoft\\Internet Explorer\\Main\\TabProcGrowth must be '0'."
63
64
#define ZONE_MY_COMPUTER L"0"
65
#define ZONE_LOCAL_INTRANET L"1"
66
#define ZONE_TRUSTED_SITES L"2"
67
#define ZONE_INTERNET L"3"
68
#define ZONE_RESTRICTED_SITES L"4"
69
70
#define IELAUNCHURL_API L"ielaunchurl"
71
#define CREATEPROCESS_API L"createprocess"
72
73
#define RUNDLL_EXE_NAME L"rundll32.exe"
74
#define INTERNET_CONTROL_PANEL_APPLET_NAME L"inetcpl.cpl"
75
#define CLEAR_CACHE_COMMAND_LINE_ARGS L"rundll32.exe %s,ClearMyTracksByProcess %u"
76
// This magic value is the combination of the following bitflags:
77
// #define CLEAR_HISTORY 0x0001 // Clears history
78
// #define CLEAR_COOKIES 0x0002 // Clears cookies
79
// #define CLEAR_CACHE 0x0004 // Clears Temporary Internet Files folder
80
// #define CLEAR_CACHE_ALL 0x0008 // Clears offline favorites and download history
81
// #define CLEAR_FORM_DATA 0x0010 // Clears saved form data for form auto-fill-in
82
// #define CLEAR_PASSWORDS 0x0020 // Clears passwords saved for websites
83
// #define CLEAR_PHISHING_FILTER 0x0040 // Clears phishing filter data
84
// #define CLEAR_RECOVERY_DATA 0x0080 // Clears webpage recovery data
85
// #define CLEAR_PRIVACY_ADVISOR 0x0800 // Clears tracking data
86
// #define CLEAR_SHOW_NO_GUI 0x0100 // Do not show a GUI when running the cache clearing
87
//
88
// Bitflags available but not used in this magic value are as follows:
89
// #define CLEAR_USE_NO_THREAD 0x0200 // Do not use multithreading for deletion
90
// #define CLEAR_PRIVATE_CACHE 0x0400 // Valid only when browser is in private browsing mode
91
// #define CLEAR_DELETE_ALL 0x1000 // Deletes data stored by add-ons
92
// #define CLEAR_PRESERVE_FAVORITES 0x2000 // Preserves cached data for "favorite" websites
93
#define CLEAR_CACHE_OPTIONS 0x09FF
94
95
namespace webdriver {
96
97
BrowserFactory::BrowserFactory(void) {
98
// Must be done in the constructor. Do not move to Initialize().
99
this->GetEdgeExecutableLocation();
100
this->GetIEExecutableLocation();
101
this->GetIEVersion();
102
this->oleacc_instance_handle_ = NULL;
103
this->edge_ie_mode_ = false;
104
this->ignore_process_match_ = false;
105
}
106
107
BrowserFactory::~BrowserFactory(void) {
108
if (this->oleacc_instance_handle_) {
109
::FreeLibrary(this->oleacc_instance_handle_);
110
}
111
}
112
113
std::string BrowserFactory::initial_browser_url(void) {
114
return StringUtilities::ToString(this->initial_browser_url_);
115
}
116
117
std::string BrowserFactory::browser_command_line_switches(void) {
118
return StringUtilities::ToString(this->browser_command_line_switches_);
119
}
120
121
void BrowserFactory::Initialize(BrowserFactorySettings settings) {
122
LOG(TRACE) << "Entering BrowserFactory::Initialize";
123
this->ignore_protected_mode_settings_ = settings.ignore_protected_mode_settings;
124
this->browser_attach_timeout_ = settings.browser_attach_timeout;
125
this->force_createprocess_api_ = settings.force_create_process_api;
126
this->force_shell_windows_api_ = settings.force_shell_windows_api;
127
this->clear_cache_ = settings.clear_cache_before_launch;
128
this->browser_command_line_switches_ = StringUtilities::ToWString(settings.browser_command_line_switches);
129
this->initial_browser_url_ = StringUtilities::ToWString(settings.initial_browser_url);
130
this->edge_ie_mode_ = settings.attach_to_edge_ie || this->ie_redirects_edge_;
131
this->ignore_process_match_ = settings.ignore_process_match;
132
this->ignore_zoom_setting_ = settings.ignore_zoom_setting || this->edge_ie_mode_;
133
LOG(DEBUG) << "path before was " << settings.edge_executable_path << "\n";
134
this->edge_executable_location_ = StringUtilities::ToWString(settings.edge_executable_path);
135
LOG(DEBUG) << "path after was " << StringUtilities::ToString(this->edge_executable_location_) << "\n";
136
this->html_getobject_msg_ = ::RegisterWindowMessage(HTML_GETOBJECT_MSG);
137
138
// Explicitly load MSAA so we know if it's installed
139
this->oleacc_instance_handle_ = ::LoadLibrary(OLEACC_LIBRARY_NAME);
140
}
141
142
void BrowserFactory::ClearCache() {
143
LOG(TRACE) << "Entering BrowserFactory::ClearCache";
144
if (this->clear_cache_) {
145
if (IsWindowsVistaOrGreater()) {
146
LOG(DEBUG) << "Clearing cache with low mandatory integrity level as required on Windows Vista or later.";
147
this->InvokeClearCacheUtility(true);
148
}
149
LOG(DEBUG) << "Clearing cache with normal process execution.";
150
this->InvokeClearCacheUtility(false);
151
}
152
}
153
154
DWORD BrowserFactory::LaunchBrowserProcess(std::string* error_message) {
155
LOG(TRACE) << "Entering BrowserFactory::LaunchBrowserProcess";
156
157
DWORD process_id = NULL;
158
bool has_valid_protected_mode_settings = false;
159
LOG(DEBUG) << "Ignoring Protected Mode Settings: "
160
<< this->ignore_protected_mode_settings_;
161
if (!this->ignore_protected_mode_settings_) {
162
LOG(DEBUG) << "Checking validity of Protected Mode settings.";
163
has_valid_protected_mode_settings = this->ProtectedModeSettingsAreValid();
164
}
165
LOG(DEBUG) << "Has Valid Protected Mode Settings: "
166
<< has_valid_protected_mode_settings;
167
if (this->ignore_protected_mode_settings_ || has_valid_protected_mode_settings) {
168
// Determine which launch API to use.
169
bool use_createprocess_api = false;
170
if (this->force_createprocess_api_) {
171
if (this->IsCreateProcessApiAvailable()) {
172
use_createprocess_api = true;
173
} else {
174
// The only time IsCreateProcessApiAvailable will return false
175
// is when the user is using IE 8 or higher, and does not have
176
// the correct registry key setting to force the same process
177
// for the enclosing window and tab processes.
178
*error_message = CREATEPROCESS_REGISTRY_ERROR_MESSAGE;
179
return NULL;
180
}
181
} else {
182
// If we have the IELaunchURL API, expressly use it. Otherwise,
183
// fall back to using CreateProcess().
184
if (!this->IsIELaunchURLAvailable()) {
185
use_createprocess_api = true;
186
}
187
}
188
189
this->ClearCache();
190
191
PROCESS_INFORMATION proc_info;
192
::ZeroMemory(&proc_info, sizeof(proc_info));
193
194
if (this->edge_ie_mode_) {
195
this->LaunchEdgeInIEMode(&proc_info, error_message);
196
} else if (!use_createprocess_api) {
197
this->LaunchBrowserUsingIELaunchURL(&proc_info, error_message);
198
} else {
199
this->LaunchBrowserUsingCreateProcess(&proc_info, error_message);
200
}
201
202
process_id = proc_info.dwProcessId;
203
if (process_id == NULL) {
204
// If whatever API we are using failed to launch the browser, we should
205
// have a NULL value in the dwProcessId member of the PROCESS_INFORMATION
206
// structure. In that case, we will have already set the approprate error
207
// message. On the off chance that we haven't yet set the appropriate
208
// error message, that means we successfully launched the browser (i.e.,
209
// the browser launch API returned a success code), but we still have a
210
// NULL process ID.
211
if (error_message->size() == 0) {
212
std::string launch_api_name = use_createprocess_api ? "The CreateProcess API" : "The IELaunchURL API";
213
*error_message = launch_api_name + NULL_PROCESS_ID_ERROR_MESSAGE;
214
}
215
} else {
216
::WaitForInputIdle(proc_info.hProcess, 2000);
217
std::string browser_launched = this->edge_ie_mode_ ? "Edge in IE Mode" : "IE";
218
LOG(DEBUG) << browser_launched << " launched successfully with process ID " << process_id;
219
std::vector<wchar_t> image_buffer(MAX_PATH);
220
int buffer_count = ::GetProcessImageFileName(proc_info.hProcess, &image_buffer[0], MAX_PATH);
221
std::wstring full_image_path = &image_buffer[0];
222
size_t last_delimiter = full_image_path.find_last_of('\\');
223
std::string image_name = StringUtilities::ToString(full_image_path.substr(last_delimiter + 1, buffer_count - last_delimiter));
224
LOG(DEBUG) << "Process with ID " << process_id << " is executing " << image_name;
225
}
226
227
if (proc_info.hThread != NULL) {
228
::CloseHandle(proc_info.hThread);
229
}
230
231
if (proc_info.hProcess != NULL) {
232
::CloseHandle(proc_info.hProcess);
233
}
234
235
} else {
236
*error_message = PROTECTED_MODE_SETTING_ERROR_MESSAGE;
237
}
238
return process_id;
239
}
240
241
bool BrowserFactory::IsIELaunchURLAvailable() {
242
LOG(TRACE) << "Entering BrowserFactory::IsIELaunchURLAvailable";
243
bool api_is_available = false;
244
HMODULE library_handle = ::LoadLibrary(IEFRAME_LIBRARY_NAME);
245
if (library_handle != NULL) {
246
FARPROC proc_address = 0;
247
proc_address = ::GetProcAddress(library_handle, IELAUNCHURL_FUNCTION_NAME);
248
if (proc_address == NULL || proc_address == 0) {
249
LOGERR(DEBUG) << "Unable to get address of " << IELAUNCHURL_FUNCTION_NAME
250
<< " method in " << IEFRAME_LIBRARY_NAME;
251
} else {
252
api_is_available = true;
253
}
254
::FreeLibrary(library_handle);
255
} else {
256
LOGERR(DEBUG) << "Unable to load library " << IEFRAME_LIBRARY_NAME;
257
}
258
return api_is_available;
259
}
260
261
void BrowserFactory::LaunchBrowserUsingIELaunchURL(PROCESS_INFORMATION* proc_info,
262
std::string* error_message) {
263
LOG(TRACE) << "Entering BrowserFactory::LaunchBrowserUsingIELaunchURL";
264
LOG(DEBUG) << "Starting IE using the IELaunchURL API";
265
HRESULT launch_result = ::IELaunchURL(this->initial_browser_url_.c_str(),
266
proc_info,
267
NULL);
268
if (FAILED(launch_result)) {
269
LOGHR(WARN, launch_result) << "Error using IELaunchURL to start IE";
270
std::wstring hresult_msg = _com_error(launch_result).ErrorMessage();
271
*error_message = StringUtilities::Format(IELAUNCHURL_ERROR_MESSAGE,
272
launch_result,
273
StringUtilities::ToString(hresult_msg).c_str(),
274
this->initial_browser_url().c_str());
275
}
276
}
277
278
bool BrowserFactory::IsCreateProcessApiAvailable() {
279
LOG(TRACE) << "Entering BrowserFactory::IsCreateProcessApiAvailable";
280
if (this->ie_major_version_ >= 8) {
281
// According to http://blogs.msdn.com/b/askie/archive/2009/03/09/opening-a-new-tab-may-launch-a-new-process-with-internet-explorer-8-0.aspx
282
// If CreateProcess() is used and TabProcGrowth != 0 IE will use different tab and frame processes.
283
// Such behaviour is not supported by AttachToBrowser().
284
// FYI, IELaunchURL() returns correct 'frame' process (but sometimes not).
285
std::wstring tab_proc_growth;
286
if (RegistryUtilities::GetRegistryValue(HKEY_CURRENT_USER,
287
IE_TABPROCGROWTH_REGISTRY_KEY,
288
L"TabProcGrowth",
289
&tab_proc_growth)) {
290
if (tab_proc_growth != L"0") {
291
// Registry value has wrong value, return false
292
return false;
293
}
294
} else {
295
// Registry key or value not found, or another error condition getting the value.
296
return false;
297
}
298
}
299
return true;
300
}
301
302
void BrowserFactory::LaunchBrowserUsingCreateProcess(PROCESS_INFORMATION* proc_info,
303
std::string* error_message) {
304
LOG(TRACE) << "Entering BrowserFactory::LaunchBrowserUsingCreateProcess";
305
LOG(DEBUG) << "Starting IE using the CreateProcess API";
306
307
STARTUPINFO start_info;
308
::ZeroMemory(&start_info, sizeof(start_info));
309
start_info.cb = sizeof(start_info);
310
311
std::wstring executable_and_url = this->ie_executable_location_;
312
if (this->browser_command_line_switches_.size() != 0) {
313
executable_and_url.append(L" ");
314
executable_and_url.append(this->browser_command_line_switches_);
315
}
316
executable_and_url.append(L" ");
317
executable_and_url.append(this->initial_browser_url_);
318
319
LOG(TRACE) << "IE starting command line is: '"
320
<< LOGWSTRING(executable_and_url) << "'.";
321
322
LPWSTR command_line = new WCHAR[executable_and_url.size() + 1];
323
wcscpy_s(command_line,
324
executable_and_url.size() + 1,
325
executable_and_url.c_str());
326
command_line[executable_and_url.size()] = L'\0';
327
BOOL create_process_result = ::CreateProcess(NULL,
328
command_line,
329
NULL,
330
NULL,
331
FALSE,
332
0,
333
NULL,
334
NULL,
335
&start_info,
336
proc_info);
337
if (!create_process_result) {
338
*error_message = StringUtilities::Format(CREATEPROCESS_ERROR_MESSAGE,
339
StringUtilities::ToString(command_line));
340
}
341
delete[] command_line;
342
}
343
344
bool BrowserFactory::DirectoryExists(std::wstring& dir_name) {
345
DWORD attribs = ::GetFileAttributes(dir_name.c_str());
346
if (attribs == INVALID_FILE_ATTRIBUTES) {
347
return false;
348
}
349
return (attribs & FILE_ATTRIBUTE_DIRECTORY);
350
}
351
352
bool BrowserFactory::CreateUniqueTempDir(std::wstring &temp_dir) {
353
// get temporary folder for the current user
354
wchar_t temp_path_array[128];
355
::GetTempPath(128, temp_path_array);
356
std::wstring temp_path = temp_path_array;
357
if (!DirectoryExists(temp_path)) {
358
return false;
359
}
360
361
// create a IEDriver temporary folder inside the user level temporary folder
362
bool temp_dir_created = false;
363
for (int i = 0; i < 10; i++) {
364
std::wstring output =
365
temp_path + L"IEDriver-" + StringUtilities::CreateGuid();
366
if (DirectoryExists(output)) {
367
continue;
368
}
369
370
::CreateDirectory(output.c_str(), NULL);
371
if (!DirectoryExists(output)) {
372
continue;
373
}
374
375
temp_dir = output;
376
temp_dir_created = true;
377
break;
378
}
379
380
return temp_dir_created;
381
}
382
383
void BrowserFactory::LaunchEdgeInIEMode(PROCESS_INFORMATION* proc_info,
384
std::string* error_message) {
385
LOG(TRACE) << "Entering BrowserFactory::LaunchEdgeInIEMode";
386
LOG(DEBUG) << "Starting Edge Chromium from the command line";
387
388
STARTUPINFO start_info;
389
::ZeroMemory(&start_info, sizeof(start_info));
390
start_info.cb = sizeof(start_info);
391
392
std::wstring executable_and_url = this->edge_executable_location_;
393
if (executable_and_url == L"") {
394
executable_and_url = this->edge_executable_located_location_; // Locate Edge via Registry if not passed
395
if (executable_and_url == L"") {
396
executable_and_url = L"msedge.exe"; // Assume it's on the path
397
}
398
}
399
400
// These flags force Edge into a mode where it will only run MSHTML
401
executable_and_url.append(L" --ie-mode-force");
402
executable_and_url.append(L" --internet-explorer-integration=iemode");
403
404
// create a temporary directory for IEDriver test
405
std::wstring temp_dir;
406
if (CreateUniqueTempDir(temp_dir)) {
407
LOG(TRACE) << L"Using temporary folder " << LOGWSTRING(temp_dir) << ".";
408
executable_and_url.append(L" --user-data-dir=" + temp_dir);
409
this->edge_user_data_dir_ = temp_dir;
410
}
411
412
// Prevent Edge from showing first run experience tab.
413
executable_and_url.append(L" --no-first-run");
414
415
// Disable Edge prelaunch and other background processes on startup.
416
executable_and_url.append(L" --no-service-autorun");
417
418
// Disable profile sync and implicit MS account sign-in.
419
executable_and_url.append(L" --disable-sync");
420
executable_and_url.append(L" --disable-features=msImplicitSignin");
421
422
// ALways allow popups for testing.
423
executable_and_url.append(L" --disable-popup-blocking");
424
425
// Ensure IE Mode tabs have a chance to shut down cleanly before the Edge process exits.
426
executable_and_url.append(L" --enable-features=msIEModeAlwaysWaitForUnload");
427
428
executable_and_url.append(L" ");
429
executable_and_url.append(this->initial_browser_url_);
430
431
LOG(TRACE) << "Edge in IE Mode starting command line is: '"
432
<< LOGWSTRING(executable_and_url) << "'.";
433
434
LPWSTR command_line = new WCHAR[executable_and_url.size() + 1];
435
wcscpy_s(command_line,
436
executable_and_url.size() + 1,
437
executable_and_url.c_str());
438
command_line[executable_and_url.size()] = L'\0';
439
BOOL create_process_result = ::CreateProcess(NULL,
440
command_line,
441
NULL,
442
NULL,
443
FALSE,
444
0,
445
NULL,
446
NULL,
447
&start_info,
448
proc_info);
449
450
451
if (!create_process_result) {
452
*error_message = CREATEPROCESS_EDGE_ERROR + StringUtilities::ToString(command_line);
453
}
454
455
delete[] command_line;
456
}
457
458
459
bool BrowserFactory::GetDocumentFromWindowHandle(HWND window_handle,
460
IHTMLDocument2** document) {
461
LOG(TRACE) << "Entering BrowserFactory::GetDocumentFromWindowHandle";
462
463
if (window_handle != NULL && this->oleacc_instance_handle_) {
464
LRESULT result;
465
466
::SendMessageTimeout(window_handle,
467
this->html_getobject_msg_,
468
0L,
469
0L,
470
SMTO_ABORTIFHUNG,
471
1000,
472
reinterpret_cast<PDWORD_PTR>(&result));
473
474
LPFNOBJECTFROMLRESULT object_pointer = reinterpret_cast<LPFNOBJECTFROMLRESULT>(::GetProcAddress(this->oleacc_instance_handle_, "ObjectFromLresult"));
475
if (object_pointer != NULL) {
476
HRESULT hr;
477
hr = (*object_pointer)(result,
478
IID_IHTMLDocument2,
479
0,
480
reinterpret_cast<void**>(document));
481
if (SUCCEEDED(hr)) {
482
return true;
483
} else {
484
LOGHR(WARN, hr) << "Unable to convert document object pointer to IHTMLDocument2 object via ObjectFromLresult";
485
}
486
} else {
487
LOG(WARN) << "Unable to get address of ObjectFromLresult method from library; GetProcAddress() for ObjectFromLresult returned NULL";
488
}
489
} else {
490
LOG(WARN) << "Window handle is invalid or OLEACC.DLL is not loaded properly";
491
}
492
return false;
493
}
494
495
bool BrowserFactory::AttachToBrowser(ProcessWindowInfo* process_window_info,
496
std::string* error_message) {
497
LOG(TRACE) << "Entering BrowserFactory::AttachToBrowser";
498
bool attached = false;
499
500
// Attempt to attach to the browser using ActiveAccessibility API
501
// first, if this fails fallback to using ShellWindows API.
502
// ActiveAccessibility fails if the Windows Desktop runs out of
503
// free space for GlobalAtoms.
504
// ShellWindows might fail if there is an IE modal dialog blocking
505
// execution (unverified).
506
if (!this->force_shell_windows_api_) {
507
LOG(DEBUG) << "Using Active Accessibility to find IWebBrowser2 interface";
508
attached = this->AttachToBrowserUsingActiveAccessibility(process_window_info,
509
error_message);
510
if (!attached) {
511
LOG(DEBUG) << "Failed to find IWebBrowser2 using ActiveAccessibility: "
512
<< *error_message;
513
// Reset the browser window handle to NULL, since we didn't attach
514
// using Active Accessibility.
515
process_window_info->hwndBrowser = NULL;
516
}
517
}
518
519
if (!attached) {
520
LOG(DEBUG) << "Using IShellWindows to find IWebBrowser2 interface";
521
attached = this->AttachToBrowserUsingShellWindows(process_window_info,
522
error_message);
523
}
524
525
if (attached) {
526
// Test for zoom level = 100%
527
int zoom_level = 100;
528
LOG(DEBUG) << "Ignoring zoom setting: " << this->ignore_zoom_setting_;
529
if (!this->ignore_zoom_setting_) {
530
zoom_level = this->GetBrowserZoomLevel(process_window_info->pBrowser);
531
}
532
if (zoom_level != 100) {
533
std::string zoom_level_error =
534
StringUtilities::Format(ZOOM_SETTING_ERROR_MESSAGE, zoom_level);
535
LOG(WARN) << zoom_level_error;
536
*error_message = zoom_level_error;
537
return false;
538
}
539
}
540
return attached;
541
}
542
543
544
bool BrowserFactory::IsBrowserProcessInitialized(DWORD process_id) {
545
ProcessWindowInfo info;
546
info.dwProcessId = process_id;
547
info.hwndBrowser = NULL;
548
info.pBrowser = NULL;
549
550
::EnumWindows(&BrowserFactory::FindBrowserWindow,
551
reinterpret_cast<LPARAM>(&info));
552
return info.hwndBrowser != NULL;
553
}
554
555
bool BrowserFactory::AttachToBrowserUsingActiveAccessibility
556
(ProcessWindowInfo* process_window_info,
557
std::string* error_message) {
558
LOG(TRACE) << "Entering BrowserFactory::AttachToBrowserUsingActiveAccessibility";
559
560
clock_t end = clock() + (this->browser_attach_timeout_ / 1000 * CLOCKS_PER_SEC);
561
while (process_window_info->hwndBrowser == NULL) {
562
if (this->browser_attach_timeout_ > 0 && (clock() > end)) {
563
break;
564
}
565
if (!this->edge_ie_mode_) {
566
::EnumWindows(&BrowserFactory::FindBrowserWindow,
567
reinterpret_cast<LPARAM>(process_window_info));
568
} else {
569
// If we're in edge_ie_mode, we need to look for different windows
570
if (this->ignore_process_match_) {
571
LOG(TRACE) << "Finding window handle for IE Mode on Edge, "
572
<< "ignoring process id match. This assumes only one "
573
<< "Edge instance is running on the host.";
574
::EnumWindows(&BrowserFactory::FindEdgeWindowIgnoringProcessMatch,
575
reinterpret_cast<LPARAM>(process_window_info));
576
} else {
577
LOG(TRACE) << "Finding window handle for IE Mode on Edge";
578
::EnumWindows(&BrowserFactory::FindEdgeWindow,
579
reinterpret_cast<LPARAM>(process_window_info));
580
581
}
582
}
583
584
if (process_window_info->hwndBrowser == NULL) {
585
::Sleep(250);
586
}
587
}
588
589
if (process_window_info->hwndBrowser == NULL) {
590
*error_message = StringUtilities::Format(ATTACH_TIMEOUT_ERROR_MESSAGE,
591
process_window_info->dwProcessId,
592
this->browser_attach_timeout_);
593
return false;
594
} else {
595
LOG(DEBUG) << "Found window handle " << process_window_info->hwndBrowser
596
<< " for window with class 'Internet Explorer_Server' belonging"
597
<< " to process with id " << process_window_info->dwProcessId;
598
}
599
600
CComPtr<IHTMLDocument2> document;
601
if (this->GetDocumentFromWindowHandle(process_window_info->hwndBrowser,
602
&document)) {
603
int get_parent_window_retry_count = 8;
604
CComPtr<IHTMLWindow2> window;
605
HRESULT hr = document->get_parentWindow(&window);
606
while (FAILED(hr) && get_parent_window_retry_count > 0) {
607
// We know we have a valid document. We *should* be able to do a
608
// document.parentWindow call to get the window. However, on the off-
609
// chance that the document exists, but IE is slow to initialize all
610
// of the COM objects and the full DOM, we'll sleep up to 2 seconds,
611
// retrying to get the parent window.
612
::Sleep(250);
613
hr = document->get_parentWindow(&window);
614
--get_parent_window_retry_count;
615
}
616
if (SUCCEEDED(hr)) {
617
// http://support.microsoft.com/kb/257717
618
CComPtr<IServiceProvider> provider;
619
window->QueryInterface<IServiceProvider>(&provider);
620
if (provider) {
621
CComPtr<IServiceProvider> child_provider;
622
hr = provider->QueryService(SID_STopLevelBrowser,
623
IID_IServiceProvider,
624
reinterpret_cast<void**>(&child_provider));
625
if (SUCCEEDED(hr)) {
626
CComPtr<IWebBrowser2> browser;
627
hr = child_provider->QueryService(SID_SWebBrowserApp,
628
IID_IWebBrowser2,
629
reinterpret_cast<void**>(&browser));
630
if (SUCCEEDED(hr)) {
631
process_window_info->pBrowser = browser.Detach();
632
return true;
633
} else {
634
LOGHR(WARN, hr) << "IServiceProvider::QueryService for SID_SWebBrowserApp failed";
635
}
636
} else {
637
LOGHR(WARN, hr) << "IServiceProvider::QueryService for SID_STopLevelBrowser failed";
638
}
639
} else {
640
LOG(WARN) << "QueryInterface for IServiceProvider failed";
641
}
642
} else {
643
LOGHR(WARN, hr) << "Call to IHTMLDocument2::get_parentWindow failed";
644
}
645
} else {
646
*error_message = "Could not get document from window handle";
647
}
648
return false;
649
}
650
651
bool BrowserFactory::AttachToBrowserUsingShellWindows(
652
ProcessWindowInfo* process_window_info,
653
std::string* error_message) {
654
LOG(TRACE) << "Entering BrowserFactory::AttachToBrowserUsingShellWindows";
655
656
CComPtr<IShellWindows> shell_windows;
657
HRESULT hr = shell_windows.CoCreateInstance(CLSID_ShellWindows);
658
if (FAILED(hr)) {
659
LOGHR(WARN, hr) << "Unable to create an object using the IShellWindows interface with CoCreateInstance";
660
return false;
661
}
662
663
CComPtr<IUnknown> enumerator_unknown;
664
hr = shell_windows->_NewEnum(&enumerator_unknown);
665
if (FAILED(hr)) {
666
LOGHR(WARN, hr) << "Unable to get enumerator from IShellWindows interface";
667
return false;
668
}
669
670
clock_t end = clock() + (this->browser_attach_timeout_ / 1000 * CLOCKS_PER_SEC);
671
672
CComPtr<IEnumVARIANT> enumerator;
673
enumerator_unknown->QueryInterface<IEnumVARIANT>(&enumerator);
674
while (process_window_info->hwndBrowser == NULL) {
675
if (this->browser_attach_timeout_ > 0 && (clock() > end)) {
676
break;
677
}
678
enumerator->Reset();
679
for (CComVariant shell_window_variant;
680
enumerator->Next(1, &shell_window_variant, NULL) == S_OK;
681
shell_window_variant.Clear()) {
682
683
if (shell_window_variant.vt != VT_DISPATCH) {
684
continue;
685
}
686
687
CComPtr<IShellBrowser> shell_browser;
688
hr = IUnknown_QueryService(shell_window_variant.pdispVal,
689
SID_STopLevelBrowser,
690
IID_PPV_ARGS(&shell_browser));
691
if (shell_browser) {
692
HWND hwnd;
693
hr = shell_browser->GetWindow(&hwnd);
694
if (SUCCEEDED(hr)) {
695
::EnumChildWindows(hwnd,
696
&BrowserFactory::FindChildWindowForProcess,
697
reinterpret_cast<LPARAM>(process_window_info));
698
if (process_window_info->hwndBrowser != NULL) {
699
LOG(DEBUG) << "Found window handle "
700
<< process_window_info->hwndBrowser
701
<< " for window with class 'Internet Explorer_Server'"
702
<< " belonging to process with id "
703
<< process_window_info->dwProcessId;
704
CComPtr<IWebBrowser2> browser;
705
hr = shell_window_variant.pdispVal->QueryInterface<IWebBrowser2>(&browser);
706
if (FAILED(hr)) {
707
LOGHR(WARN, hr) << "Found browser window using ShellWindows "
708
<< "API, but QueryInterface for IWebBrowser2 "
709
<< "failed, so could not attach to the browser.";
710
} else {
711
process_window_info->pBrowser = browser.Detach();
712
}
713
break;
714
}
715
}
716
}
717
}
718
if (process_window_info->hwndBrowser == NULL ||
719
process_window_info->pBrowser == NULL) {
720
::Sleep(250);
721
}
722
}
723
724
if (process_window_info->hwndBrowser == NULL) {
725
*error_message = StringUtilities::Format(ATTACH_TIMEOUT_ERROR_MESSAGE,
726
process_window_info->dwProcessId,
727
this->browser_attach_timeout_);
728
return false;
729
}
730
731
if (process_window_info->pBrowser == NULL) {
732
*error_message = ATTACH_FAILURE_ERROR_MESSAGE;
733
return false;
734
}
735
return true;
736
}
737
738
int BrowserFactory::GetBrowserZoomLevel(IWebBrowser2* browser) {
739
LOG(TRACE) << "Entering BrowserFactory::GetBrowserZoomLevel";
740
clock_t end = clock() + (this->browser_attach_timeout_ / 1000 * CLOCKS_PER_SEC);
741
CComPtr<IDispatch> document_dispatch;
742
while (!document_dispatch) {
743
if (this->browser_attach_timeout_ > 0 && (clock() > end)) {
744
break;
745
}
746
747
browser->get_Document(&document_dispatch);
748
749
if (!document_dispatch) {
750
::Sleep(250);
751
}
752
}
753
754
if (!document_dispatch) {
755
LOG(WARN) << "Call to IWebBrowser2::get_Document failed";
756
return 0;
757
}
758
759
CComPtr<IHTMLDocument2> document;
760
document_dispatch->QueryInterface(&document);
761
if (!document) {
762
LOG(WARN) << "QueryInterface for IHTMLDocument2 failed.";
763
return 0;
764
}
765
766
CComPtr<IHTMLWindow2> window;
767
HRESULT hr = document->get_parentWindow(&window);
768
if (FAILED(hr)) {
769
LOGHR(WARN, hr) << "Call to IHTMLDocument2::get_parentWindow failed";
770
return 0;
771
}
772
773
// Test for zoom level = 100%
774
int zoom_level = this->GetZoomLevel(document, window);
775
return zoom_level;
776
}
777
778
int BrowserFactory::GetZoomLevel(IHTMLDocument2* document, IHTMLWindow2* window) {
779
LOG(TRACE) << "Entering BrowserFactory::GetZoomLevel";
780
int zoom = 100; // Chances are the zoom level hasn't been modified....
781
HRESULT hr = S_OK;
782
if (this->ie_major_version_ == 7) {
783
CComPtr<IHTMLElement> body;
784
hr = document->get_body(&body);
785
if (FAILED(hr)) {
786
LOGHR(WARN, hr) << "Call to IHTMLDocument2::get_body failed";
787
return zoom;
788
}
789
790
long offset_width = 0;
791
hr = body->get_offsetWidth(&offset_width);
792
if (FAILED(hr)) {
793
LOGHR(WARN, hr) << "Call to IHTMLElement::get_offsetWidth failed";
794
return zoom;
795
}
796
797
CComPtr<IHTMLElement2> body2;
798
hr = body.QueryInterface<IHTMLElement2>(&body2);
799
if (FAILED(hr)) {
800
LOGHR(WARN, hr) << "Attempt to QueryInterface for IHTMLElement2 failed";
801
return zoom;
802
}
803
804
CComPtr<IHTMLRect> rect;
805
hr = body2->getBoundingClientRect(&rect);
806
if (FAILED(hr)) {
807
LOGHR(WARN, hr) << "Call to IHTMLElement2::getBoundingClientRect failed";
808
return zoom;
809
}
810
811
long left = 0, right = 0;
812
hr = rect->get_left(&left);
813
if (FAILED(hr)) {
814
LOGHR(WARN, hr) << "Call to IHTMLRect::get_left failed";
815
return zoom;
816
}
817
818
hr = rect->get_right(&right);
819
if (FAILED(hr)) {
820
LOGHR(WARN, hr) << "Call to IHTMLRect::get_right failed";
821
return zoom;
822
}
823
824
zoom = static_cast<int>((static_cast<double>(right - left) / offset_width) * 100.0);
825
} else if (this->ie_major_version_ >= 8) {
826
CComPtr<IHTMLScreen> screen;
827
hr = window->get_screen(&screen);
828
if (FAILED(hr)) {
829
LOGHR(WARN, hr) << "Call to IHTMLWindow2::get_screen failed";
830
return zoom;
831
}
832
833
CComPtr<IHTMLScreen2> screen2;
834
hr = screen.QueryInterface<IHTMLScreen2>(&screen2);
835
if (FAILED(hr)) {
836
LOGHR(WARN, hr) << "Attempt to QueryInterface for IHTMLScreen2 failed";
837
return zoom;
838
}
839
840
long device_xdpi=0, logical_xdpi = 0;
841
hr = screen2->get_deviceXDPI(&device_xdpi);
842
if (FAILED(hr)) {
843
LOGHR(WARN, hr) << "Call to IHTMLScreen2::get_deviceXDPI failed";
844
return zoom;
845
}
846
847
hr = screen2->get_logicalXDPI(&logical_xdpi);
848
if (FAILED(hr)) {
849
LOGHR(WARN, hr) << "Call to IHTMLScreen2::get_logicalXDPI failed";
850
return zoom;
851
}
852
853
zoom = static_cast<int>((static_cast<double>(device_xdpi) / logical_xdpi) * 100.0);
854
} else {
855
// IE6 case
856
zoom = 100;
857
}
858
859
LOG(DEBUG) << "Browser zoom level is " << zoom << "%";
860
return zoom;
861
}
862
863
IWebBrowser2* BrowserFactory::CreateBrowser(bool is_protected_mode) {
864
LOG(TRACE) << "Entering BrowserFactory::CreateBrowser";
865
866
IWebBrowser2* browser = NULL;
867
DWORD context = CLSCTX_LOCAL_SERVER;
868
if (this->ie_major_version_ == 7 && IsWindowsVistaOrGreater()) {
869
// ONLY for IE 7 on Windows Vista. XP and below do not have Protected Mode;
870
// Windows 7 shipped with IE8.
871
context = context | CLSCTX_ENABLE_CLOAKING;
872
}
873
874
HRESULT hr = S_OK;
875
if (is_protected_mode) {
876
hr = ::CoCreateInstance(CLSID_InternetExplorer,
877
NULL,
878
context,
879
IID_IWebBrowser2,
880
reinterpret_cast<void**>(&browser));
881
} else {
882
hr = ::CoCreateInstance(CLSID_InternetExplorerMedium,
883
NULL,
884
context,
885
IID_IWebBrowser2,
886
reinterpret_cast<void**>(&browser));
887
}
888
// When IWebBrowser2::Quit() is called, the wrapper process doesn't
889
// exit right away. When that happens, CoCreateInstance can fail while
890
// the abandoned iexplore.exe instance is still valid. The "right" way
891
// to do this would be to call ::EnumProcesses before calling
892
// CoCreateInstance, finding all of the iexplore.exe processes, waiting
893
// for one to exit, and then proceed. However, there is no way to tell
894
// if a process ID belongs to an Internet Explorer instance, particularly
895
// when a 32-bit process tries to enumerate 64-bit processes on 64-bit
896
// Windows. So, we'll take the brute force way out, just retrying the call
897
// to CoCreateInstance until it succeeds (the old iexplore.exe process has
898
// exited), or we get a different error code. We'll also set a 45-second
899
// timeout, with 45 seconds being chosen because it's below the default
900
// 60 second HTTP request timeout of most language bindings.
901
if (FAILED(hr) && HRESULT_CODE(hr) == ERROR_SHUTDOWN_IS_SCHEDULED) {
902
LOG(DEBUG) << "CoCreateInstance for IWebBrowser2 failed due to a "
903
<< "browser process that has not yet fully exited. Retrying "
904
<< "until the browser process exits and a new instance can "
905
<< "be successfully created.";
906
}
907
clock_t timeout = clock() + (45 * CLOCKS_PER_SEC);
908
while (FAILED(hr) &&
909
HRESULT_CODE(hr) == ERROR_SHUTDOWN_IS_SCHEDULED &&
910
clock() < timeout) {
911
::Sleep(500);
912
hr = ::CoCreateInstance(CLSID_InternetExplorer,
913
NULL,
914
context,
915
IID_IWebBrowser2,
916
reinterpret_cast<void**>(&browser));
917
}
918
if (FAILED(hr) && HRESULT_CODE(hr) != ERROR_SHUTDOWN_IS_SCHEDULED) {
919
// If we hit this branch, the CoCreateInstance failed due to an unexpected
920
// error, either before we looped, or at some point during the loop. In
921
// in either case, there's not much else we can do except log the failure.
922
LOGHR(WARN, hr) << "CoCreateInstance for IWebBrowser2 failed.";
923
}
924
925
if (browser != NULL) {
926
browser->put_Visible(VARIANT_TRUE);
927
}
928
929
return browser;
930
}
931
932
bool BrowserFactory::CreateLowIntegrityLevelToken(HANDLE* process_token_handle,
933
HANDLE* mic_token_handle,
934
PSID* sid) {
935
LOG(TRACE) << "Entering BrowserFactory::CreateLowIntegrityLevelToken";
936
BOOL result = TRUE;
937
TOKEN_MANDATORY_LABEL tml = {0};
938
939
HANDLE process_handle = ::GetCurrentProcess();
940
result = ::OpenProcessToken(process_handle,
941
MAXIMUM_ALLOWED,
942
process_token_handle);
943
944
if (result) {
945
result = ::DuplicateTokenEx(*process_token_handle,
946
MAXIMUM_ALLOWED,
947
NULL,
948
SecurityImpersonation,
949
TokenPrimary,
950
mic_token_handle);
951
if (!result) {
952
LOGERR(WARN) << "CreateLowIntegrityLevelToken: Could not duplicate token";
953
::CloseHandle(*process_token_handle);
954
}
955
}
956
957
if (result) {
958
result = ::ConvertStringSidToSid(SDDL_ML_LOW, sid);
959
if (result) {
960
tml.Label.Attributes = SE_GROUP_INTEGRITY;
961
tml.Label.Sid = *sid;
962
} else {
963
LOGERR(WARN) << "CreateLowIntegrityLevelToken: Could not convert string SID to SID";
964
::CloseHandle(*process_token_handle);
965
::CloseHandle(*mic_token_handle);
966
}
967
}
968
969
if(result) {
970
result = ::SetTokenInformation(*mic_token_handle,
971
TokenIntegrityLevel,
972
&tml,
973
sizeof(tml) + ::GetLengthSid(*sid));
974
if (!result) {
975
LOGERR(WARN) << "CreateLowIntegrityLevelToken: Could not set token information to low level";
976
::CloseHandle(*process_token_handle);
977
::CloseHandle(*mic_token_handle);
978
::LocalFree(*sid);
979
}
980
}
981
982
::CloseHandle(process_handle);
983
return result == TRUE;
984
}
985
986
void BrowserFactory::InvokeClearCacheUtility(bool use_low_integrity_level) {
987
LOG(TRACE) << "Entering BrowserFactory::InvokeClearCacheUtility";
988
HRESULT hr = S_OK;
989
std::vector<wchar_t> system_path_buffer(MAX_PATH);
990
std::vector<wchar_t> rundll_exe_path_buffer(MAX_PATH);
991
std::vector<wchar_t> inetcpl_path_buffer(MAX_PATH);
992
std::wstring args = L"";
993
994
UINT system_path_size = ::GetSystemDirectory(&system_path_buffer[0], MAX_PATH);
995
996
HANDLE process_token = NULL;
997
HANDLE mic_token = NULL;
998
PSID sid = NULL;
999
1000
bool can_create_process = true;
1001
if (!use_low_integrity_level ||
1002
this->CreateLowIntegrityLevelToken(&process_token, &mic_token, &sid)) {
1003
if (0 != system_path_size &&
1004
system_path_size <= static_cast<int>(system_path_buffer.size())) {
1005
if (::PathCombine(&rundll_exe_path_buffer[0],
1006
&system_path_buffer[0],
1007
RUNDLL_EXE_NAME) &&
1008
::PathCombine(&inetcpl_path_buffer[0],
1009
&system_path_buffer[0],
1010
INTERNET_CONTROL_PANEL_APPLET_NAME)) {
1011
// PathCombine will return NULL if the buffer would be exceeded.
1012
::PathQuoteSpaces(&rundll_exe_path_buffer[0]);
1013
::PathQuoteSpaces(&inetcpl_path_buffer[0]);
1014
args = StringUtilities::Format(CLEAR_CACHE_COMMAND_LINE_ARGS,
1015
&inetcpl_path_buffer[0],
1016
CLEAR_CACHE_OPTIONS);
1017
} else {
1018
LOG(WARN) << "Cannot combine paths to utilities required to clear cache.";
1019
can_create_process = false;
1020
}
1021
} else {
1022
LOG(WARN) << "Paths system directory exceeds MAX_PATH.";
1023
can_create_process = false;
1024
}
1025
1026
if (can_create_process) {
1027
LOG(DEBUG) << "Launching inetcpl.cpl via rundll32.exe to clear cache";
1028
STARTUPINFO start_info;
1029
::ZeroMemory(&start_info, sizeof(start_info));
1030
start_info.cb = sizeof(start_info);
1031
1032
PROCESS_INFORMATION process_info;
1033
BOOL is_process_created = FALSE;
1034
start_info.dwFlags = STARTF_USESHOWWINDOW;
1035
start_info.wShowWindow = SW_SHOWNORMAL;
1036
1037
std::vector<wchar_t> args_buffer(0);
1038
StringUtilities::ToBuffer(args, &args_buffer);
1039
// Create the process to run with low or medium rights
1040
if (use_low_integrity_level) {
1041
is_process_created = CreateProcessAsUser(mic_token,
1042
&rundll_exe_path_buffer[0],
1043
&args_buffer[0],
1044
NULL,
1045
NULL,
1046
FALSE,
1047
0,
1048
NULL,
1049
NULL,
1050
&start_info,
1051
&process_info);
1052
} else {
1053
is_process_created = CreateProcess(&rundll_exe_path_buffer[0],
1054
&args_buffer[0],
1055
NULL,
1056
NULL,
1057
FALSE,
1058
0,
1059
NULL,
1060
NULL,
1061
&start_info,
1062
&process_info);
1063
}
1064
1065
if (is_process_created) {
1066
// Wait for the rundll32.exe process to exit.
1067
LOG(DEBUG) << "Waiting for rundll32.exe process to exit.";
1068
::WaitForInputIdle(process_info.hProcess, 5000);
1069
::WaitForSingleObject(process_info.hProcess, 30000);
1070
::CloseHandle(process_info.hProcess);
1071
::CloseHandle(process_info.hThread);
1072
LOG(DEBUG) << "Cache clearing complete.";
1073
} else {
1074
LOGERR(WARN) << "Could not create process for clearing cache.";
1075
}
1076
}
1077
1078
// Close the handles opened when creating the
1079
// low integrity level token
1080
if (use_low_integrity_level) {
1081
::CloseHandle(process_token);
1082
::CloseHandle(mic_token);
1083
::LocalFree(sid);
1084
}
1085
}
1086
}
1087
1088
BOOL CALLBACK BrowserFactory::FindBrowserWindow(HWND hwnd, LPARAM arg) {
1089
// Could this be an IE instance?
1090
// 8 == "IeFrame\0"
1091
// 21 == "Shell DocObject View\0"
1092
// 19 == "Chrome_WidgetWin_1"
1093
char name[21];
1094
if (::GetClassNameA(hwnd, name, 21) == 0) {
1095
// No match found. Skip
1096
return TRUE;
1097
}
1098
1099
if (strcmp(IE_FRAME_WINDOW_CLASS, name) != 0 &&
1100
strcmp(SHELL_DOCOBJECT_VIEW_WINDOW_CLASS, name) != 0 &&
1101
strcmp(ANDIE_FRAME_WINDOW_CLASS, name) != 0) {
1102
return TRUE;
1103
}
1104
1105
return EnumChildWindows(hwnd, FindChildWindowForProcess, arg);
1106
}
1107
1108
BOOL CALLBACK BrowserFactory::FindEdgeWindow(HWND hwnd, LPARAM arg) {
1109
// Could this be an EdgeChrome window?
1110
// 19 == "Chrome_WidgetWin_1"
1111
char name[20];
1112
if (::GetClassNameA(hwnd, name, 20) == 0) {
1113
// No match found. Skip
1114
return TRUE;
1115
}
1116
1117
// continue if it is not "Chrome_WidgetWin_1"
1118
if (strcmp(ANDIE_FRAME_WINDOW_CLASS, name) != 0) return TRUE;
1119
1120
// continue if window does not belong to the target process
1121
DWORD process_id = NULL;
1122
::GetWindowThreadProcessId(hwnd, &process_id);
1123
ProcessWindowInfo* process_window_info = reinterpret_cast<ProcessWindowInfo*>(arg);
1124
if (process_window_info->dwProcessId != process_id) {
1125
return TRUE;
1126
}
1127
1128
return EnumChildWindows(hwnd, FindEdgeChildWindowForProcess, arg);
1129
}
1130
1131
BOOL CALLBACK BrowserFactory::FindEdgeWindowIgnoringProcessMatch(HWND hwnd, LPARAM arg) {
1132
// Could this be an EdgeChrome window?
1133
// 19 == "Chrome_WidgetWin_1"
1134
char name[20];
1135
if (::GetClassNameA(hwnd, name, 20) == 0) {
1136
// No match found. Skip
1137
return TRUE;
1138
}
1139
1140
// continue if it is not "Chrome_WidgetWin_1"
1141
if (strcmp(ANDIE_FRAME_WINDOW_CLASS, name) != 0) return TRUE;
1142
1143
return EnumChildWindows(hwnd, FindEdgeChildWindowForProcess, arg);
1144
}
1145
1146
BOOL CALLBACK BrowserFactory::FindIEBrowserHandles(HWND hwnd, LPARAM arg) {
1147
std::vector<HWND>* handles = reinterpret_cast<std::vector<HWND>*>(arg);
1148
1149
// Could this be an Internet Explorer Server window?
1150
// 25 == "Internet Explorer_Server\0"
1151
char name[25];
1152
if (::GetClassNameA(hwnd, name, 25) == 0) {
1153
// No match found. Skip
1154
return TRUE;
1155
}
1156
1157
if (strcmp("Internet Explorer_Server", name) == 0) {
1158
handles->push_back(hwnd);
1159
}
1160
1161
return TRUE;
1162
}
1163
1164
BOOL CALLBACK BrowserFactory::FindEdgeBrowserHandles(HWND hwnd, LPARAM arg) {
1165
std::vector<HWND>* handles = reinterpret_cast<std::vector<HWND>*>(arg);
1166
1167
// Could this be an Internet Explorer Server window?
1168
// 19 == "Chrome_WidgetWin_1\0"
1169
char name[20];
1170
if (::GetClassNameA(hwnd, name, 20) == 0) {
1171
// No match found. Skip
1172
return TRUE;
1173
}
1174
1175
if (strcmp("Chrome_WidgetWin_1", name) == 0) {
1176
handles->push_back(hwnd);
1177
}
1178
1179
return TRUE;
1180
}
1181
1182
BOOL CALLBACK BrowserFactory::FindChildWindowForProcess(HWND hwnd, LPARAM arg) {
1183
ProcessWindowInfo *process_window_info = reinterpret_cast<ProcessWindowInfo*>(arg);
1184
1185
// Could this be an Internet Explorer Server window?
1186
// 25 == "Internet Explorer_Server\0"
1187
char name[25];
1188
if (::GetClassNameA(hwnd, name, 25) == 0) {
1189
// No match found. Skip
1190
return TRUE;
1191
}
1192
1193
if (strcmp(IE_SERVER_CHILD_WINDOW_CLASS, name) != 0) {
1194
return TRUE;
1195
} else {
1196
DWORD process_id = NULL;
1197
::GetWindowThreadProcessId(hwnd, &process_id);
1198
LOG(DEBUG) << "Looking for " << process_window_info->dwProcessId;
1199
if (process_window_info->dwProcessId == process_id) {
1200
// Once we've found the first Internet Explorer_Server window
1201
// for the process we want, we can stop.
1202
process_window_info->hwndBrowser = hwnd;
1203
return FALSE;
1204
}
1205
}
1206
1207
return TRUE;
1208
}
1209
1210
BOOL CALLBACK BrowserFactory::FindEdgeChildWindowForProcess(HWND hwnd, LPARAM arg) {
1211
ProcessWindowInfo* process_window_info = reinterpret_cast<ProcessWindowInfo*>(arg);
1212
1213
// Could this be an Internet Explorer Server window?
1214
// 25 == "Internet Explorer_Server\0"
1215
char name[25];
1216
if (::GetClassNameA(hwnd, name, 25) == 0) {
1217
// No match found. Skip
1218
return TRUE;
1219
}
1220
1221
if (strcmp(IE_SERVER_CHILD_WINDOW_CLASS, name) != 0) {
1222
return TRUE;
1223
}
1224
else {
1225
DWORD process_id = NULL;
1226
::GetWindowThreadProcessId(hwnd, &process_id);
1227
LOG(DEBUG) << "Looking for " << process_window_info->dwProcessId;
1228
// Once we've found the first Internet Explorer_Server window
1229
// for the process we want, we can stop.
1230
process_window_info->hwndBrowser = hwnd;
1231
return FALSE;
1232
}
1233
1234
return TRUE;
1235
}
1236
1237
BOOL CALLBACK BrowserFactory::FindDialogWindowForProcess(HWND hwnd, LPARAM arg) {
1238
ProcessWindowInfo* process_win_info = reinterpret_cast<ProcessWindowInfo*>(arg);
1239
1240
// Could this be an dialog window?
1241
// 7 == "#32770\0"
1242
// 29 == "Credential Dialog Xaml Host\0"
1243
// 34 == "Internet Explorer_TridentDlgFrame\0"
1244
char name[34];
1245
if (::GetClassNameA(hwnd, name, 34) == 0) {
1246
// No match found. Skip
1247
return TRUE;
1248
}
1249
1250
if (strcmp(ALERT_WINDOW_CLASS, name) != 0 &&
1251
strcmp(HTML_DIALOG_WINDOW_CLASS, name) != 0 &&
1252
strcmp(SECURITY_DIALOG_WINDOW_CLASS, name) != 0) {
1253
return TRUE;
1254
} else {
1255
// If the window style has the WS_DISABLED bit set or the
1256
// WS_VISIBLE bit unset, it can't be handled via the UI,
1257
// and must not be a visible dialog. Furthermore, if the
1258
// window style does not display a caption bar, it's not a
1259
// dialog displayed by the browser, but likely by an add-on
1260
// (like an antivirus toolbar). Note that checking the caption
1261
// window style is a hack, and may begin to fail if IE ever
1262
// changes the style of its alert windows.
1263
long window_long_style = ::GetWindowLong(hwnd, GWL_STYLE);
1264
if ((window_long_style & WS_DISABLED) != 0 ||
1265
(window_long_style & WS_VISIBLE) == 0 ||
1266
(window_long_style & WS_CAPTION) == 0) {
1267
return TRUE;
1268
}
1269
DWORD process_id = NULL;
1270
::GetWindowThreadProcessId(hwnd, &process_id);
1271
if (process_win_info->dwProcessId == process_id) {
1272
// Once we've found the first dialog (#32770) window
1273
// for the process we want, we can stop.
1274
process_win_info->hwndBrowser = hwnd;
1275
return FALSE;
1276
}
1277
}
1278
1279
return TRUE;
1280
}
1281
1282
void BrowserFactory::GetIEExecutableLocation() {
1283
LOG(TRACE) << "Entering BrowserFactory::GetIEExecutableLocation";
1284
1285
std::wstring redirection;
1286
if (RegistryUtilities::GetRegistryValue(HKEY_LOCAL_MACHINE,
1287
IE_REDIRECT,
1288
L"{1FD49718-1D00-4B19-AF5F-070AF6D5D54C}",
1289
&redirection)) {
1290
this->ie_redirects_edge_ = redirection == L"1";
1291
}
1292
else {
1293
LOG(WARN) << "Unable to determine IE to Edge Redirection";
1294
}
1295
1296
std::wstring class_id;
1297
if (RegistryUtilities::GetRegistryValue(HKEY_LOCAL_MACHINE,
1298
IE_CLSID_REGISTRY_KEY,
1299
L"",
1300
&class_id)) {
1301
std::wstring location_key = L"SOFTWARE\\Classes\\CLSID\\" +
1302
class_id +
1303
L"\\LocalServer32";
1304
std::wstring executable_location;
1305
1306
// If we are a 32-bit driver instance, running on 64-bit Windows,
1307
// we want to bypass the registry redirection so that we can get
1308
// the actual location of the browser executable. The primary place
1309
// this matters is when getting the browser version; the secondary
1310
// place is if the user specifies to use the CreateProcess API for
1311
// launching the browser, hence the 'true' argument in the following
1312
// call to RegistryUtilities::GetRegistryValue.
1313
if (RegistryUtilities::GetRegistryValue(HKEY_LOCAL_MACHINE,
1314
location_key,
1315
L"",
1316
true,
1317
&executable_location)) {
1318
// If the executable location in the registry has an environment
1319
// variable in it, expand the environment variable to an absolute
1320
// path.
1321
DWORD expanded_location_size = ::ExpandEnvironmentStrings(executable_location.c_str(), NULL, 0);
1322
std::vector<wchar_t> expanded_location(expanded_location_size);
1323
::ExpandEnvironmentStrings(executable_location.c_str(), &expanded_location[0], expanded_location_size);
1324
executable_location = &expanded_location[0];
1325
this->ie_executable_location_ = executable_location;
1326
size_t arg_start_pos = executable_location.find(L" -");
1327
if (arg_start_pos != std::string::npos) {
1328
this->ie_executable_location_ = executable_location.substr(0, arg_start_pos);
1329
}
1330
if (this->ie_executable_location_.substr(0, 1) == L"\"") {
1331
this->ie_executable_location_.erase(0, 1);
1332
this->ie_executable_location_.erase(this->ie_executable_location_.size() - 1, 1);
1333
}
1334
} else {
1335
LOG(WARN) << "Unable to get IE executable location from registry";
1336
}
1337
} else {
1338
LOG(WARN) << "Unable to get IE class id from registry";
1339
}
1340
}
1341
1342
void BrowserFactory::GetEdgeExecutableLocation() {
1343
LOG(TRACE) << "Entering BrowserFactory::GetEdgeExecutableLocation";
1344
std::wstring edge_executable_location;
1345
if (RegistryUtilities::GetRegistryValue(HKEY_LOCAL_MACHINE,
1346
EDGE_REGISTRY_KEY,
1347
L"",
1348
true,
1349
&edge_executable_location)) {
1350
// If the executable location in the registry has an environment
1351
// variable in it, expand the environment variable to an absolute
1352
// path.
1353
DWORD expanded_location_size = ::ExpandEnvironmentStrings(edge_executable_location.c_str(), NULL, 0);
1354
std::vector<wchar_t> expanded_location(expanded_location_size);
1355
::ExpandEnvironmentStrings(edge_executable_location.c_str(), &expanded_location[0], expanded_location_size);
1356
edge_executable_location = &expanded_location[0];
1357
this->edge_executable_located_location_ = edge_executable_location;
1358
size_t arg_start_pos = edge_executable_location.find(L" -");
1359
if (arg_start_pos != std::string::npos) {
1360
this->edge_executable_located_location_ = edge_executable_location.substr(0, arg_start_pos);
1361
}
1362
if (this->edge_executable_located_location_.substr(0, 1) == L"\"") {
1363
this->edge_executable_located_location_.erase(0, 1);
1364
this->edge_executable_located_location_.erase(this->edge_executable_located_location_.size() - 1, 1);
1365
}
1366
} else {
1367
LOG(WARN) << "Unable to get Edge executable location from registry";
1368
}
1369
}
1370
1371
void BrowserFactory::GetIEVersion() {
1372
LOG(TRACE) << "Entering BrowserFactory::GetIEVersion";
1373
1374
std::string ie_version = FileUtilities::GetFileVersion(this->ie_executable_location_);
1375
1376
if (ie_version.size() == 0) {
1377
// 64-bit Windows 8 has a bug where it does not return the executable location properly
1378
this->ie_major_version_ = -1;
1379
LOG(WARN) << "Couldn't find IE version for executable "
1380
<< LOGWSTRING(this->ie_executable_location_)
1381
<< ", falling back to "
1382
<< this->ie_major_version_;
1383
return;
1384
}
1385
1386
std::stringstream version_stream(ie_version);
1387
version_stream >> this->ie_major_version_;
1388
}
1389
1390
bool BrowserFactory::ProtectedModeSettingsAreValid() {
1391
LOG(TRACE) << "Entering BrowserFactory::ProtectedModeSettingsAreValid";
1392
1393
bool settings_are_valid = true;
1394
LOG(DEBUG) << "Detected IE version: " << this->ie_major_version_
1395
<< ", Windows version supports Protected Mode: "
1396
<< IsWindowsVistaOrGreater() ? "true" : "false";
1397
// Only need to check Protected Mode settings on IE 7 or higher
1398
// and on Windows Vista or higher. Otherwise, Protected Mode
1399
// doesn't come into play, and are valid.
1400
// Documentation of registry settings can be found at the following
1401
// Microsoft KnowledgeBase article:
1402
// http://support.microsoft.com/kb/182569
1403
if (this->ie_major_version_ >= 7 && IsWindowsVistaOrGreater()) {
1404
HKEY key_handle;
1405
if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_CURRENT_USER,
1406
IE_SECURITY_ZONES_REGISTRY_KEY,
1407
0,
1408
KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS,
1409
&key_handle)) {
1410
DWORD subkey_count = 0;
1411
DWORD max_subkey_name_length = 0;
1412
if (ERROR_SUCCESS == ::RegQueryInfoKey(key_handle,
1413
NULL,
1414
NULL,
1415
NULL,
1416
&subkey_count,
1417
&max_subkey_name_length,
1418
NULL,
1419
NULL,
1420
NULL,
1421
NULL,
1422
NULL,
1423
NULL)) {
1424
int protected_mode_value = -1;
1425
std::vector<wchar_t> subkey_name_buffer(max_subkey_name_length + 1);
1426
for (size_t index = 0; index < subkey_count; ++index) {
1427
DWORD number_of_characters_copied = max_subkey_name_length + 1;
1428
::RegEnumKeyEx(key_handle,
1429
static_cast<DWORD>(index),
1430
&subkey_name_buffer[0],
1431
&number_of_characters_copied,
1432
NULL,
1433
NULL,
1434
NULL,
1435
NULL);
1436
std::wstring subkey_name = &subkey_name_buffer[0];
1437
// Ignore the "My Computer" zone, since it's not displayed
1438
// in the UI.
1439
if (subkey_name != ZONE_MY_COMPUTER) {
1440
int value = this->GetZoneProtectedModeSetting(key_handle,
1441
subkey_name);
1442
if (protected_mode_value == -1) {
1443
protected_mode_value = value;
1444
} else {
1445
if (value != protected_mode_value) {
1446
settings_are_valid = false;
1447
break;
1448
}
1449
}
1450
}
1451
}
1452
} else {
1453
LOG(WARN) << "RegQueryInfoKey to get count of zone setting subkeys failed";
1454
}
1455
::RegCloseKey(key_handle);
1456
} else {
1457
std::wstring registry_key_string = IE_SECURITY_ZONES_REGISTRY_KEY;
1458
LOG(WARN) << "RegOpenKeyEx for zone settings registry key "
1459
<< LOGWSTRING(registry_key_string)
1460
<< " in HKEY_CURRENT_USER failed";
1461
}
1462
}
1463
return settings_are_valid;
1464
}
1465
1466
int BrowserFactory::GetZoneProtectedModeSetting(const HKEY key_handle,
1467
const std::wstring& zone_subkey_name) {
1468
LOG(TRACE) << "Entering BrowserFactory::GetZoneProtectedModeSetting";
1469
1470
int protected_mode_value = 3;
1471
HKEY subkey_handle;
1472
if (ERROR_SUCCESS == ::RegOpenKeyEx(key_handle,
1473
zone_subkey_name.c_str(),
1474
0,
1475
KEY_QUERY_VALUE,
1476
&subkey_handle)) {
1477
DWORD value = 0;
1478
DWORD value_length = sizeof(DWORD);
1479
if (ERROR_SUCCESS == ::RegQueryValueEx(subkey_handle,
1480
IE_PROTECTED_MODE_SETTING_VALUE_NAME,
1481
NULL,
1482
NULL,
1483
reinterpret_cast<LPBYTE>(&value),
1484
&value_length)) {
1485
LOG(DEBUG) << "Found Protected Mode setting value of "
1486
<< value << " for zone " << LOGWSTRING(zone_subkey_name);
1487
protected_mode_value = value;
1488
} else {
1489
LOG(DEBUG) << "RegQueryValueEx failed for getting Protected Mode setting for a zone: "
1490
<< LOGWSTRING(zone_subkey_name);
1491
}
1492
::RegCloseKey(subkey_handle);
1493
} else {
1494
// The REG_DWORD value doesn't exist, so we have to return the default
1495
// value, which is "on" for the Internet and Restricted Sites zones and
1496
// is "on" for the Local Intranet zone in IE7 only (the default was
1497
// changed to "off" for Local Intranet in IE8), and "off" everywhere
1498
// else.
1499
// Note that a value of 0 in the registry value indicates that Protected
1500
// Mode is "on" for that zone; a value of 3 indicates that Protected Mode
1501
// is "off" for that zone.
1502
if (zone_subkey_name == ZONE_INTERNET ||
1503
zone_subkey_name == ZONE_RESTRICTED_SITES ||
1504
(zone_subkey_name == ZONE_LOCAL_INTRANET && this->ie_major_version_ == 7)) {
1505
protected_mode_value = 0;
1506
}
1507
LOG(DEBUG) << "Protected Mode zone setting value does not exist for zone "
1508
<< LOGWSTRING(zone_subkey_name) << ". Using default value of "
1509
<< protected_mode_value;
1510
}
1511
return protected_mode_value;
1512
}
1513
1514
bool BrowserFactory::IsWindowsVersionOrGreater(unsigned short major_version,
1515
unsigned short minor_version,
1516
unsigned short service_pack) {
1517
OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0,{ 0 }, 0, 0 };
1518
DWORDLONG const dwlConditionMask = VerSetConditionMask(
1519
VerSetConditionMask(
1520
VerSetConditionMask(
1521
0, VER_MAJORVERSION, VER_GREATER_EQUAL),
1522
VER_MINORVERSION, VER_GREATER_EQUAL),
1523
VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
1524
1525
osvi.dwMajorVersion = major_version;
1526
osvi.dwMinorVersion = minor_version;
1527
osvi.wServicePackMajor = service_pack;
1528
1529
return VerifyVersionInfoW(&osvi,
1530
VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,
1531
dwlConditionMask) != FALSE;
1532
}
1533
1534
bool BrowserFactory::IsWindowsVistaOrGreater() {
1535
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0);
1536
}
1537
1538
bool BrowserFactory::IsEdgeMode() const {
1539
return this->edge_ie_mode_;
1540
}
1541
1542
// delete a folder recursively
1543
int BrowserFactory::DeleteDirectory(const std::wstring &dir_name) {
1544
WIN32_FIND_DATA file_info;
1545
1546
std::wstring file_pattern = dir_name + L"\\*.*";
1547
HANDLE file_handle = ::FindFirstFile(file_pattern.c_str(), &file_info);
1548
if (file_handle != INVALID_HANDLE_VALUE) {
1549
do {
1550
if (file_info.cFileName[0] == '.') {
1551
continue;
1552
}
1553
std::wstring file_path = dir_name + L"\\" + file_info.cFileName;
1554
1555
if (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1556
int return_value = DeleteDirectory(file_path);
1557
if (return_value) {
1558
return return_value;
1559
}
1560
} else {
1561
if (::SetFileAttributes(file_path.c_str(), FILE_ATTRIBUTE_NORMAL) == FALSE) {
1562
return ::GetLastError();
1563
}
1564
1565
if (::DeleteFile(file_path.c_str()) == FALSE) {
1566
return ::GetLastError();
1567
}
1568
}
1569
} while (::FindNextFile(file_handle, &file_info) == TRUE);
1570
1571
::FindClose(file_handle);
1572
DWORD dwError = ::GetLastError();
1573
if (dwError != ERROR_NO_MORE_FILES) {
1574
return dwError;
1575
}
1576
1577
if (::SetFileAttributes(dir_name.c_str(), FILE_ATTRIBUTE_NORMAL) == FALSE) {
1578
return ::GetLastError();
1579
}
1580
1581
if (::RemoveDirectory(dir_name.c_str()) == FALSE) {
1582
return ::GetLastError();
1583
}
1584
}
1585
1586
return 0;
1587
}
1588
1589
std::wstring BrowserFactory::GetEdgeTempDir() {
1590
return this->edge_user_data_dir_;
1591
}
1592
1593
} // namespace webdriver
1594
1595