Skip to content

Commit 4a50934

Browse files
Stanley Honjimevans
authored andcommitted
Add basic support for IEDriver to drive an EdgeChromium IE tab
Signed-off-by: Jim Evans <[email protected]>
1 parent 26d8b67 commit 4a50934

File tree

8 files changed

+191
-10
lines changed

8 files changed

+191
-10
lines changed

cpp/iedriver/Browser.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@
3434

3535
namespace webdriver {
3636

37-
Browser::Browser(IWebBrowser2* browser, HWND hwnd, HWND session_handle) : DocumentHost(hwnd, session_handle) {
37+
Browser::Browser(IWebBrowser2* browser, HWND hwnd, HWND session_handle, bool is_edge_chromium) : DocumentHost(hwnd, session_handle) {
3838
LOG(TRACE) << "Entering Browser::Browser";
3939
this->is_explicit_close_requested_ = false;
4040
this->is_navigation_started_ = false;
4141
this->browser_ = browser;
4242
this->AttachEvents();
43+
this->is_edge_chromium_ = is_edge_chromium;
4344
}
4445

4546
Browser::~Browser(void) {
@@ -112,6 +113,12 @@ void __stdcall Browser::NewWindow3(IDispatch** ppDisp,
112113
DWORD dwFlags,
113114
BSTR bstrUrlContext,
114115
BSTR bstrUrl) {
116+
if (this->is_edge_chromium_) {
117+
LOG(TRACE) << "Entering Browser::NewWindow3 but early exiting due to edge mode";
118+
// In Edge Chromium, we do not yet support attaching to new windows.
119+
// Quit early and ignore that event.
120+
return;
121+
}
115122
LOG(TRACE) << "Entering Browser::NewWindow3";
116123
// Handle the NewWindow3 event to allow us to immediately hook
117124
// the events of the new browser window opened by the user action.
@@ -400,7 +407,14 @@ void Browser::Close() {
400407
// Closing the browser, so having focus on a frame doesn't
401408
// make any sense.
402409
this->SetFocusedFrameByElement(NULL);
403-
HRESULT hr = this->browser_->Quit();
410+
411+
HRESULT hr = S_OK;
412+
if (this->is_edge_chromium_) {
413+
hr = PostMessage(GetTopLevelWindowHandle(), WM_CLOSE, 0, 0);
414+
} else {
415+
hr = this->browser_->Quit();
416+
}
417+
404418
if (FAILED(hr)) {
405419
LOGHR(WARN, hr) << "Call to IWebBrowser2::Quit failed";
406420
}

cpp/iedriver/Browser.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class ElementRepository;
4444

4545
class Browser : public DocumentHost, public IDispEventSimpleImpl<1, Browser, &DIID_DWebBrowserEvents2> {
4646
public:
47-
Browser(IWebBrowser2* browser, HWND hwnd, HWND session_handle);
47+
Browser(IWebBrowser2* browser, HWND hwnd, HWND session_handle, bool isEdgeChrome = false);
4848
virtual ~Browser(void);
4949

5050
static inline _ATL_FUNC_INFO* BeforeNavigate2Info() {
@@ -159,6 +159,7 @@ class Browser : public DocumentHost, public IDispEventSimpleImpl<1, Browser, &DI
159159
CComPtr<IWebBrowser2> browser_;
160160
bool is_navigation_started_;
161161
bool is_explicit_close_requested_;
162+
bool is_edge_chromium_;
162163
std::vector<DWORD> known_process_ids_;
163164
};
164165

cpp/iedriver/BrowserFactory.cpp

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#define IE_FRAME_WINDOW_CLASS "IEFrame"
4242
#define SHELL_DOCOBJECT_VIEW_WINDOW_CLASS "Shell DocObject View"
4343
#define IE_SERVER_CHILD_WINDOW_CLASS "Internet Explorer_Server"
44+
#define ANDIE_FRAME_WINDOW_CLASS "Chrome_WidgetWin_1"
4445

4546
#define IE_CLSID_REGISTRY_KEY L"SOFTWARE\\Classes\\InternetExplorer.Application\\CLSID"
4647
#define IE_SECURITY_ZONES_REGISTRY_KEY L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Zones"
@@ -50,6 +51,7 @@
5051

5152
#define IELAUNCHURL_ERROR_MESSAGE "IELaunchURL() returned HRESULT %X ('%s') for URL '%s'"
5253
#define CREATEPROCESS_ERROR_MESSAGE "CreateProcess() failed for command line '%s'"
54+
#define CREATEPROCESS_EDGE_ERROR "CreateProcess() failed for edge with the following command: "
5355
#define NULL_PROCESS_ID_ERROR_MESSAGE " successfully launched Internet Explorer, but did not return a valid process ID."
5456
#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."
5557
#define ZOOM_SETTING_ERROR_MESSAGE "Browser zoom level was set to %d%%. It should be set to 100%%"
@@ -95,6 +97,7 @@ BrowserFactory::BrowserFactory(void) {
9597
this->GetExecutableLocation();
9698
this->GetIEVersion();
9799
this->oleacc_instance_handle_ = NULL;
100+
this->edge_ie_mode_ = false;
98101
}
99102

100103
BrowserFactory::~BrowserFactory(void) {
@@ -121,7 +124,10 @@ void BrowserFactory::Initialize(BrowserFactorySettings settings) {
121124
this->clear_cache_ = settings.clear_cache_before_launch;
122125
this->browser_command_line_switches_ = StringUtilities::ToWString(settings.browser_command_line_switches);
123126
this->initial_browser_url_ = StringUtilities::ToWString(settings.initial_browser_url);
124-
127+
this->edge_ie_mode_ = settings.attach_to_edge_ie;
128+
LOG(DEBUG) << "path before was " << settings.edge_executable_path << "\n";
129+
this->edge_executable_location_ = StringUtilities::ToWString(settings.edge_executable_path);
130+
LOG(DEBUG) << "path after was " << this->edge_executable_location_.c_str() << "\n";
125131
this->html_getobject_msg_ = ::RegisterWindowMessage(HTML_GETOBJECT_MSG);
126132

127133
// Explicitly load MSAA so we know if it's installed
@@ -180,7 +186,9 @@ DWORD BrowserFactory::LaunchBrowserProcess(std::string* error_message) {
180186
PROCESS_INFORMATION proc_info;
181187
::ZeroMemory(&proc_info, sizeof(proc_info));
182188

183-
if (!use_createprocess_api) {
189+
if (this->edge_ie_mode_) {
190+
this->LaunchEdgeInIEMode(&proc_info, error_message);
191+
} else if (!use_createprocess_api) {
184192
this->LaunchBrowserUsingIELaunchURL(&proc_info, error_message);
185193
} else {
186194
this->LaunchBrowserUsingCreateProcess(&proc_info, error_message);
@@ -327,6 +335,55 @@ void BrowserFactory::LaunchBrowserUsingCreateProcess(PROCESS_INFORMATION* proc_i
327335
delete[] command_line;
328336
}
329337

338+
void BrowserFactory::LaunchEdgeInIEMode(PROCESS_INFORMATION* proc_info,
339+
std::string* error_message) {
340+
LOG(TRACE) << "Entering BrowserFactory::LaunchEdgeInIEMode";
341+
LOG(DEBUG) << "Starting Edge Chromium from the command line";
342+
343+
STARTUPINFO start_info;
344+
::ZeroMemory(&start_info, sizeof(start_info));
345+
start_info.cb = sizeof(start_info);
346+
347+
std::wstring executable_and_url = this->edge_executable_location_;
348+
if (executable_and_url == L"") {
349+
executable_and_url = L"msedge.exe"; // Assume it's on the path if it's not passed
350+
}
351+
352+
// These flags force Edge into a mode where it will only run MSHTML
353+
executable_and_url.append(L" --ie-mode-force");
354+
executable_and_url.append(L" --internet-explorer-integration=iemode");
355+
356+
executable_and_url.append(L" ");
357+
executable_and_url.append(this->initial_browser_url_);
358+
359+
LOG(TRACE) << "IE starting command line is: '"
360+
<< LOGWSTRING(executable_and_url) << "'.";
361+
362+
LPWSTR command_line = new WCHAR[executable_and_url.size() + 1];
363+
wcscpy_s(command_line,
364+
executable_and_url.size() + 1,
365+
executable_and_url.c_str());
366+
command_line[executable_and_url.size()] = L'\0';
367+
BOOL create_process_result = ::CreateProcess(NULL,
368+
command_line,
369+
NULL,
370+
NULL,
371+
FALSE,
372+
0,
373+
NULL,
374+
NULL,
375+
&start_info,
376+
proc_info);
377+
378+
379+
if (!create_process_result) {
380+
*error_message = CREATEPROCESS_EDGE_ERROR + StringUtilities::ToString(command_line);
381+
}
382+
383+
delete[] command_line;
384+
}
385+
386+
330387
bool BrowserFactory::GetDocumentFromWindowHandle(HWND window_handle,
331388
IHTMLDocument2** document) {
332389
LOG(TRACE) << "Entering BrowserFactory::GetDocumentFromWindowHandle";
@@ -411,11 +468,13 @@ bool BrowserFactory::AttachToBrowser(ProcessWindowInfo* process_window_info,
411468
return attached;
412469
}
413470

471+
414472
bool BrowserFactory::IsBrowserProcessInitialized(DWORD process_id) {
415473
ProcessWindowInfo info;
416474
info.dwProcessId = process_id;
417475
info.hwndBrowser = NULL;
418476
info.pBrowser = NULL;
477+
419478
::EnumWindows(&BrowserFactory::FindBrowserWindow,
420479
reinterpret_cast<LPARAM>(&info));
421480
return info.hwndBrowser != NULL;
@@ -431,8 +490,15 @@ bool BrowserFactory::AttachToBrowserUsingActiveAccessibility
431490
if (this->browser_attach_timeout_ > 0 && (clock() > end)) {
432491
break;
433492
}
434-
::EnumWindows(&BrowserFactory::FindBrowserWindow,
435-
reinterpret_cast<LPARAM>(process_window_info));
493+
if (!this->edge_ie_mode_) {
494+
::EnumWindows(&BrowserFactory::FindBrowserWindow,
495+
reinterpret_cast<LPARAM>(process_window_info));
496+
} else {
497+
// If we're in edge_ie_mode, we need to look for different windows
498+
::EnumWindows(&BrowserFactory::FindEdgeWindow,
499+
reinterpret_cast<LPARAM>(process_window_info));
500+
}
501+
436502
if (process_window_info->hwndBrowser == NULL) {
437503
::Sleep(250);
438504
}
@@ -940,21 +1006,37 @@ void BrowserFactory::InvokeClearCacheUtility(bool use_low_integrity_level) {
9401006
BOOL CALLBACK BrowserFactory::FindBrowserWindow(HWND hwnd, LPARAM arg) {
9411007
// Could this be an IE instance?
9421008
// 8 == "IeFrame\0"
943-
// 21 == "Shell DocObject View\0";
1009+
// 21 == "Shell DocObject View\0"
1010+
// 19 == "Chrome_WidgetWin_1"
9441011
char name[21];
9451012
if (::GetClassNameA(hwnd, name, 21) == 0) {
9461013
// No match found. Skip
9471014
return TRUE;
9481015
}
9491016

950-
if (strcmp(IE_FRAME_WINDOW_CLASS, name) != 0 &&
1017+
if (strcmp(IE_FRAME_WINDOW_CLASS, name) != 0 &&
9511018
strcmp(SHELL_DOCOBJECT_VIEW_WINDOW_CLASS, name) != 0) {
9521019
return TRUE;
9531020
}
9541021

9551022
return EnumChildWindows(hwnd, FindChildWindowForProcess, arg);
9561023
}
9571024

1025+
BOOL CALLBACK BrowserFactory::FindEdgeWindow(HWND hwnd, LPARAM arg) {
1026+
// Could this be an EdgeChrome window?
1027+
// 19 == "Chrome_WidgetWin_1"
1028+
char name[20];
1029+
if (::GetClassNameA(hwnd, name, 20) == 0) {
1030+
// No match found. Skip
1031+
return TRUE;
1032+
}
1033+
1034+
// continue if it is not "Chrome_WidgetWin_1"
1035+
if (strcmp(ANDIE_FRAME_WINDOW_CLASS, name) != 0) return TRUE;
1036+
1037+
return EnumChildWindows(hwnd, FindEdgeChildWindowForProcess, arg);
1038+
}
1039+
9581040
BOOL CALLBACK BrowserFactory::FindChildWindowForProcess(HWND hwnd, LPARAM arg) {
9591041
ProcessWindowInfo *process_window_info = reinterpret_cast<ProcessWindowInfo*>(arg);
9601042

@@ -971,6 +1053,7 @@ BOOL CALLBACK BrowserFactory::FindChildWindowForProcess(HWND hwnd, LPARAM arg) {
9711053
} else {
9721054
DWORD process_id = NULL;
9731055
::GetWindowThreadProcessId(hwnd, &process_id);
1056+
LOG(DEBUG) << "Looking for " << process_window_info->dwProcessId;
9741057
if (process_window_info->dwProcessId == process_id) {
9751058
// Once we've found the first Internet Explorer_Server window
9761059
// for the process we want, we can stop.
@@ -982,6 +1065,33 @@ BOOL CALLBACK BrowserFactory::FindChildWindowForProcess(HWND hwnd, LPARAM arg) {
9821065
return TRUE;
9831066
}
9841067

1068+
BOOL CALLBACK BrowserFactory::FindEdgeChildWindowForProcess(HWND hwnd, LPARAM arg) {
1069+
ProcessWindowInfo* process_window_info = reinterpret_cast<ProcessWindowInfo*>(arg);
1070+
1071+
// Could this be an Internet Explorer Server window?
1072+
// 25 == "Internet Explorer_Server\0"
1073+
char name[25];
1074+
if (::GetClassNameA(hwnd, name, 25) == 0) {
1075+
// No match found. Skip
1076+
return TRUE;
1077+
}
1078+
1079+
if (strcmp(IE_SERVER_CHILD_WINDOW_CLASS, name) != 0) {
1080+
return TRUE;
1081+
}
1082+
else {
1083+
DWORD process_id = NULL;
1084+
::GetWindowThreadProcessId(hwnd, &process_id);
1085+
LOG(DEBUG) << "Looking for " << process_window_info->dwProcessId;
1086+
// Once we've found the first Internet Explorer_Server window
1087+
// for the process we want, we can stop.
1088+
process_window_info->hwndBrowser = hwnd;
1089+
return FALSE;
1090+
}
1091+
1092+
return TRUE;
1093+
}
1094+
9851095
BOOL CALLBACK BrowserFactory::FindDialogWindowForProcess(HWND hwnd, LPARAM arg) {
9861096
ProcessWindowInfo* process_win_info = reinterpret_cast<ProcessWindowInfo*>(arg);
9871097

@@ -1243,5 +1353,8 @@ bool BrowserFactory::IsWindowsVistaOrGreater() {
12431353
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0);
12441354
}
12451355

1356+
bool BrowserFactory::IsEdgeMode() const {
1357+
return this->edge_ie_mode_;
1358+
}
12461359

12471360
} // namespace webdriver

cpp/iedriver/BrowserFactory.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ struct BrowserFactorySettings {
3636
int browser_attach_timeout;
3737
std::string initial_browser_url;
3838
std::string browser_command_line_switches;
39+
bool attach_to_edge_ie; // Used to attach to EdgeChromium IE processes
40+
std::string edge_executable_path;
3941
};
4042

4143
class BrowserFactory {
4244
public:
4345
BrowserFactory(void);
4446
virtual ~BrowserFactory(void);
4547

48+
4649
void Initialize(BrowserFactorySettings settings);
4750

4851
DWORD LaunchBrowserProcess(std::string* error_message);
@@ -65,12 +68,17 @@ class BrowserFactory {
6568
int browser_version(void) const { return this->ie_major_version_; }
6669

6770
static BOOL CALLBACK FindChildWindowForProcess(HWND hwnd, LPARAM arg);
71+
static BOOL CALLBACK FindEdgeChildWindowForProcess(HWND hwnd, LPARAM arg);
6872
static BOOL CALLBACK FindDialogWindowForProcess(HWND hwnd, LPARAM arg);
6973

7074
static bool IsWindowsVistaOrGreater(void);
7175

76+
bool IsEdgeMode(void) const;
7277
private:
7378
static BOOL CALLBACK FindBrowserWindow(HWND hwnd, LPARAM param);
79+
80+
81+
static BOOL CALLBACK FindEdgeWindow(HWND hwnd, LPARAM param);
7482
static bool IsWindowsVersionOrGreater(unsigned short major_version,
7583
unsigned short minor_version,
7684
unsigned short service_pack);
@@ -97,6 +105,8 @@ class BrowserFactory {
97105
int GetZoomLevel(IHTMLDocument2* document, IHTMLWindow2* window);
98106
void LaunchBrowserUsingCreateProcess(PROCESS_INFORMATION* proc_info,
99107
std::string* error_message);
108+
void LaunchEdgeInIEMode(PROCESS_INFORMATION* proc_info,
109+
std::string* error_message);
100110
void LaunchBrowserUsingIELaunchURL(PROCESS_INFORMATION* proc_info,
101111
std::string* error_message);
102112
bool IsIELaunchURLAvailable(void);
@@ -116,6 +126,9 @@ class BrowserFactory {
116126

117127
int ie_major_version_;
118128
std::wstring ie_executable_location_;
129+
130+
bool edge_ie_mode_;
131+
std::wstring edge_executable_location_;
119132
};
120133

121134
} // namespace webdriver

cpp/iedriver/CommandHandlers/NewSessionCommandHandler.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ void NewSessionCommandHandler::ExecuteInternal(
4242

4343
// Find W3C capabilities first.
4444
IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor);
45+
4546
ParametersMap::const_iterator it = command_parameters.find("capabilities");
4647
if (it != command_parameters.end()) {
4748
LOG(DEBUG) << "Found W3C capabilities structure";
@@ -411,8 +412,24 @@ void NewSessionCommandHandler::SetBrowserFactorySettings(const IECommandExecutor
411412
false);
412413
factory_settings.clear_cache_before_launch = ensure_clean_session.asBool();
413414

415+
// By default, we should not be attaching to edge_ie
416+
factory_settings.attach_to_edge_ie = false;
417+
Json::Value attach_to_edgechrome = this->GetCapability(capabilities,
418+
ATTACH_TO_EDGE_CHROME,
419+
Json::booleanValue,
420+
false);
421+
factory_settings.attach_to_edge_ie = attach_to_edgechrome.asBool();
422+
423+
Json::Value edge_executable_path = this->GetCapability(capabilities,
424+
EDGE_EXECUTABLE_PATH,
425+
Json::stringValue,
426+
Json::Value(Json::stringValue));
427+
factory_settings.edge_executable_path = edge_executable_path.asString();
428+
414429
IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor);
415430
mutable_executor.browser_factory()->Initialize(factory_settings);
431+
mutable_executor.set_is_edge_mode(factory_settings.attach_to_edge_ie);
432+
mutable_executor.set_edge_executable_path(factory_settings.edge_executable_path);
416433
}
417434
}
418435

@@ -552,6 +569,8 @@ Json::Value NewSessionCommandHandler::CreateReturnedCapabilities(const IECommand
552569
ie_options[ELEMENT_SCROLL_BEHAVIOR_CAPABILITY] = executor.input_manager()->scroll_behavior();
553570
ie_options[REQUIRE_WINDOW_FOCUS_CAPABILITY] = executor.input_manager()->require_window_focus();
554571
ie_options[FILE_UPLOAD_DIALOG_TIMEOUT_CAPABILITY] = executor.file_upload_dialog_timeout();
572+
ie_options[ATTACH_TO_EDGE_CHROME] = executor.is_edge_mode();
573+
ie_options[EDGE_EXECUTABLE_PATH] = executor.edge_executable_path();
555574

556575
if (executor.proxy_manager()->is_proxy_set()) {
557576
ie_options[USE_PER_PROCESS_PROXY_CAPABILITY] = executor.proxy_manager()->use_per_process_proxy();

0 commit comments

Comments
 (0)