Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/cpp/iedriver/Browser.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 "Browser.h"
18
19
#include <comutil.h>
20
#include <ShlGuid.h>
21
22
#include "errorcodes.h"
23
#include "logging.h"
24
25
#include "Alert.h"
26
#include "BrowserFactory.h"
27
#include "CustomTypes.h"
28
#include "messages.h"
29
#include "HookProcessor.h"
30
#include "Script.h"
31
#include "StringUtilities.h"
32
#include "WebDriverConstants.h"
33
#include "WindowUtilities.h"
34
35
namespace webdriver {
36
37
Browser::Browser(IWebBrowser2* browser, HWND hwnd, HWND session_handle, bool is_edge_chromium) : DocumentHost(hwnd, session_handle) {
38
LOG(TRACE) << "Entering Browser::Browser";
39
this->is_explicit_close_requested_ = false;
40
this->is_navigation_started_ = false;
41
this->browser_ = browser;
42
this->AttachEvents();
43
this->set_is_edge_chromium(is_edge_chromium);
44
}
45
46
Browser::~Browser(void) {
47
this->DetachEvents();
48
}
49
50
void __stdcall Browser::BeforeNavigate2(IDispatch* pObject,
51
VARIANT* pvarUrl,
52
VARIANT* pvarFlags,
53
VARIANT* pvarTargetFrame,
54
VARIANT* pvarData,
55
VARIANT* pvarHeaders,
56
VARIANT_BOOL* pbCancel) {
57
LOG(TRACE) << "Entering Browser::BeforeNavigate2";
58
std::wstring url(pvarUrl->bstrVal);
59
60
LOG(DEBUG) << "BeforeNavigate2: Url: " << LOGWSTRING(url) << ", TargetFrame: " << pvarTargetFrame->bstrVal;
61
}
62
63
void __stdcall Browser::OnQuit() {
64
LOG(TRACE) << "Entering Browser::OnQuit";
65
if (!this->is_explicit_close_requested_) {
66
if (this->is_awaiting_new_process()) {
67
LOG(WARN) << "A new browser process was requested. This means a Protected "
68
<< "Mode boundary has been crossed, and that future commands to "
69
<< "the current browser instance will fail. The driver will "
70
<< "attempt to reconnect to the newly created browser object, "
71
<< "but there is no guarantee it will work.";
72
DWORD process_id;
73
HWND window_handle = this->GetBrowserWindowHandle();
74
::GetWindowThreadProcessId(window_handle, &process_id);
75
76
BrowserReattachInfo* info = new BrowserReattachInfo;
77
info->browser_id = this->browser_id();
78
info->current_process_id = process_id;
79
info->known_process_ids = this->known_process_ids_;
80
81
this->DetachEvents();
82
this->browser_ = NULL;
83
::PostMessage(this->executor_handle(),
84
WD_BROWSER_REATTACH,
85
NULL,
86
reinterpret_cast<LPARAM>(info));
87
return;
88
} else {
89
LOG(WARN) << "This instance of Internet Explorer (" << this->browser_id()
90
<< ") is exiting without an explicit request to close it. "
91
<< "Unless you clicked a link that specifically attempts to "
92
<< "close the page, that likely means a Protected Mode "
93
<< "boundary has been crossed (either entering or exiting "
94
<< "Protected Mode). It is highly likely that any subsequent "
95
<< "commands to this driver instance will fail. THIS IS NOT A "
96
<< "BUG IN THE IE DRIVER! Fix your code and/or browser "
97
<< "configuration so that a Protected Mode boundary is not "
98
<< "crossed.";
99
}
100
}
101
this->PostQuitMessage();
102
}
103
104
void __stdcall Browser::NewProcess(DWORD lCauseFlag,
105
IDispatch* pWB2,
106
VARIANT_BOOL* pbCancel) {
107
LOG(TRACE) << "Entering Browser::NewProcess";
108
this->InitiateBrowserReattach();
109
}
110
111
void __stdcall Browser::NewWindow3(IDispatch** ppDisp,
112
VARIANT_BOOL* pbCancel,
113
DWORD dwFlags,
114
BSTR bstrUrlContext,
115
BSTR bstrUrl) {
116
LOG(TRACE) << "Entering Browser::NewWindow3";
117
::PostMessage(this->executor_handle(), WD_BEFORE_NEW_WINDOW, NULL, NULL);
118
std::vector<HWND>* ie_window_handles = nullptr;
119
WPARAM param_flag = 0;
120
121
if (this->is_edge_chromium()) {
122
param_flag = 1000;
123
//HWND top_level_handle = this->GetTopLevelWindowHandle();
124
// 1) find all Edge browser window handles
125
std::vector<HWND>edge_window_handles;
126
::EnumWindows(&BrowserFactory::FindEdgeBrowserHandles,
127
reinterpret_cast<LPARAM>(&edge_window_handles));
128
129
// 2) find all IE browser window handlers as child window when Edge runs in IEMode
130
ie_window_handles = new std::vector<HWND>;
131
for (HWND& edge_window_handle : edge_window_handles) {
132
std::vector<HWND> child_window_handles;
133
::EnumChildWindows(edge_window_handle,
134
&BrowserFactory::FindIEBrowserHandles,
135
reinterpret_cast<LPARAM>(&child_window_handles));
136
137
for (HWND& child_window_handle : child_window_handles) {
138
ie_window_handles->push_back(child_window_handle);
139
}
140
}
141
} else {
142
// Handle the NewWindow3 event to allow us to immediately hook
143
// the events of the new browser window opened by the user action.
144
// The three ways we can respond to this event are documented at
145
// http://msdn.microsoft.com/en-us/library/aa768337%28v=vs.85%29.aspx
146
// We potentially use two of those response methods.
147
// This will not allow us to handle windows created by the JavaScript
148
// showModalDialog function().
149
std::wstring url = bstrUrl;
150
IWebBrowser2* browser;
151
NewWindowInfo info;
152
info.target_url = StringUtilities::ToString(url);
153
LRESULT create_result = ::SendMessage(this->executor_handle(),
154
WD_BROWSER_NEW_WINDOW,
155
NULL,
156
reinterpret_cast<LPARAM>(&info));
157
if (create_result != 0) {
158
// The new, blank IWebBrowser2 object was not created,
159
// so we can't really do anything appropriate here.
160
// Note this is "response method 2" of the aforementioned
161
// documentation.
162
LOG(WARN) << "A valid IWebBrowser2 object could not be created.";
163
*pbCancel = VARIANT_TRUE;
164
::PostMessage(this->executor_handle(), WD_AFTER_NEW_WINDOW, NULL, NULL);
165
return;
166
}
167
168
// We received a valid IWebBrowser2 pointer, so deserialize it onto this
169
// thread, and pass the result back to the caller.
170
HRESULT hr = ::CoGetInterfaceAndReleaseStream(info.browser_stream,
171
IID_IWebBrowser2,
172
reinterpret_cast<void**>(&browser));
173
if (FAILED(hr)) {
174
LOGHR(WARN, hr) << "Failed to marshal IWebBrowser2 interface from stream.";
175
}
176
177
*ppDisp = browser;
178
}
179
180
// 3) pass all IE window handles to WD_AFTER_NEW_WINDOW
181
::PostMessage(this->executor_handle(),
182
WD_AFTER_NEW_WINDOW,
183
param_flag,
184
reinterpret_cast<LPARAM>(ie_window_handles));
185
}
186
187
void __stdcall Browser::DocumentComplete(IDispatch* pDisp, VARIANT* URL) {
188
LOG(TRACE) << "Entering Browser::DocumentComplete";
189
190
// Flag the browser as navigation having started.
191
this->is_navigation_started_ = true;
192
193
// DocumentComplete fires last for the top-level frame. If it fires
194
// for the top-level frame and the focused_frame_window_ member variable
195
// is not NULL, we assume we have navigated from within a frameset to a
196
// link that has a target of "_top", which replaces the frameset with the
197
// target page. On a top-level navigation, we are supposed to reset the
198
// focused frame to the top-level, so we do that here.
199
// NOTE: This is a possible source of unreliability if the above
200
// assumptions turn out to be wrong and/or the event firing doesn't work
201
// the way we expect it to.
202
CComPtr<IDispatch> dispatch(this->browser_);
203
if (dispatch.IsEqualObject(pDisp)) {
204
if (this->focused_frame_window() != NULL) {
205
LOG(DEBUG) << "DocumentComplete happened from within a frameset";
206
this->SetFocusedFrameByElement(NULL);
207
}
208
}
209
}
210
211
void Browser::InitiateBrowserReattach() {
212
LOG(TRACE) << "Entering Browser::InitiateBrowserReattach";
213
LOG(DEBUG) << "Requesting browser reattach";
214
this->known_process_ids_.clear();
215
WindowUtilities::GetProcessesByName(L"iexplore.exe",
216
&this->known_process_ids_);
217
this->set_is_awaiting_new_process(true);
218
::SendMessage(this->executor_handle(), WD_BEFORE_BROWSER_REATTACH, NULL, NULL);
219
}
220
221
void Browser::ReattachBrowser(IWebBrowser2* browser) {
222
LOG(TRACE) << "Entering Browser::ReattachBrowser";
223
this->browser_ = browser;
224
this->AttachEvents();
225
this->set_is_awaiting_new_process(false);
226
LOG(DEBUG) << "Reattach complete";
227
}
228
229
void Browser::GetDocument(IHTMLDocument2** doc) {
230
this->GetDocument(false, doc);
231
}
232
233
void Browser::GetDocument(const bool force_top_level_document,
234
IHTMLDocument2** doc) {
235
LOG(TRACE) << "Entering Browser::GetDocument";
236
CComPtr<IHTMLWindow2> window;
237
238
if (this->focused_frame_window() == NULL || force_top_level_document) {
239
LOG(INFO) << "No child frame focus. Focus is on top-level frame";
240
241
CComPtr<IDispatch> dispatch;
242
HRESULT hr = this->browser_->get_Document(&dispatch);
243
if (FAILED(hr)) {
244
LOGHR(WARN, hr) << "Unable to get document, IWebBrowser2::get_Document call failed";
245
return;
246
}
247
248
CComPtr<IHTMLDocument2> dispatch_doc;
249
hr = dispatch->QueryInterface(&dispatch_doc);
250
if (FAILED(hr)) {
251
LOGHR(WARN, hr) << "Have document but cannot cast, IDispatch::QueryInterface call failed";
252
return;
253
}
254
255
dispatch_doc->get_parentWindow(&window);
256
} else {
257
window = this->focused_frame_window();
258
}
259
260
if (window) {
261
bool result = this->GetDocumentFromWindow(window, doc);
262
if (!result) {
263
LOG(WARN) << "Cannot get document";
264
}
265
} else {
266
LOG(WARN) << "No window is found";
267
}
268
}
269
270
std::string Browser::GetTitle() {
271
LOG(TRACE) << "Entering Browser::GetTitle";
272
273
CComPtr<IDispatch> dispatch;
274
HRESULT hr = this->browser_->get_Document(&dispatch);
275
if (FAILED(hr)) {
276
LOGHR(WARN, hr) << "Unable to get document, IWebBrowser2::get_Document call failed";
277
return "";
278
}
279
280
CComPtr<IHTMLDocument2> doc;
281
hr = dispatch->QueryInterface(&doc);
282
if (FAILED(hr)) {
283
LOGHR(WARN, hr) << "Have document but cannot cast, IDispatch::QueryInterface call failed";
284
return "";
285
}
286
287
CComBSTR title;
288
hr = doc->get_title(&title);
289
if (FAILED(hr)) {
290
LOGHR(WARN, hr) << "Unable to get document title, call to IHTMLDocument2::get_title failed";
291
return "";
292
}
293
294
std::wstring converted_title = title;
295
std::string title_string = StringUtilities::ToString(converted_title);
296
return title_string;
297
}
298
299
std::string Browser::GetBrowserUrl() {
300
LOG(TRACE) << "Entering Browser::GetBrowserUrl";
301
302
CComBSTR url;
303
HRESULT hr = this->browser_->get_LocationURL(&url);
304
if (FAILED(hr)) {
305
LOGHR(WARN, hr) << "Unable to get current URL, call to IWebBrowser2::get_LocationURL failed";
306
return "";
307
}
308
309
std::wstring converted_url = url;
310
std::string current_url = StringUtilities::ToString(converted_url);
311
return current_url;
312
}
313
314
HWND Browser::GetContentWindowHandle() {
315
LOG(TRACE) << "Entering Browser::GetContentWindowHandle";
316
317
HWND current_content_window_handle = this->window_handle();
318
// If this window is closing, the only reason to care about
319
// a valid window handle is to check for alerts whose parent
320
// is this window handle, so return the stored window handle.
321
if (!this->is_closing()) {
322
// If, for some reason, the window handle is no longer valid, set the
323
// member variable to NULL so that we can reacquire the valid window
324
// handle. Note that this can happen when browsing from one type of
325
// content to another, like from HTML to a transformed XML page that
326
// renders content. If the member variable is NULL upon entering this
327
// method, that is okay, as it typically means only that this object
328
// is newly constructed, and has not yet had its handle set.
329
bool window_handle_is_valid = ::IsWindow(current_content_window_handle);
330
if (!window_handle_is_valid) {
331
LOG(INFO) << "Flushing window handle as it is no longer valid";
332
this->set_window_handle(NULL);
333
}
334
335
if (this->window_handle() == NULL) {
336
LOG(INFO) << "Restore window handle from tab";
337
// GetBrowserWindowHandle gets the TabWindowClass window in IE 7 and 8,
338
// and the top-level window frame in IE 6. The window we need is the
339
// InternetExplorer_Server window.
340
HWND tab_window_handle = this->GetBrowserWindowHandle();
341
if (tab_window_handle == NULL) {
342
LOG(WARN) << "No tab window found";
343
}
344
HWND content_window_handle = this->FindContentWindowHandle(tab_window_handle);
345
this->set_window_handle(content_window_handle);
346
}
347
}
348
349
return this->window_handle();
350
}
351
352
std::string Browser::GetWindowName() {
353
LOG(TRACE) << "Entering Browser::GetWindowName";
354
355
CComPtr<IDispatch> dispatch;
356
HRESULT hr = this->browser_->get_Document(&dispatch);
357
if (FAILED(hr)) {
358
LOGHR(WARN, hr) << "Unable to get document, IWebBrowser2::get_Document call failed";
359
return "";
360
}
361
362
CComPtr<IHTMLDocument2> doc;
363
dispatch->QueryInterface<IHTMLDocument2>(&doc);
364
if (!doc) {
365
LOGHR(WARN, hr) << "Have document but cannot cast, IDispatch::QueryInterface call failed";
366
return "";
367
}
368
369
CComPtr<IHTMLWindow2> window;
370
hr = doc->get_parentWindow(&window);
371
if (FAILED(hr)) {
372
LOGHR(WARN, hr) << "Unable to get parent window, call to IHTMLDocument2::get_parentWindow failed";
373
return "";
374
}
375
376
std::string name = "";
377
CComBSTR window_name;
378
hr = window->get_name(&window_name);
379
if (window_name) {
380
std::wstring converted_window_name = window_name;
381
name = StringUtilities::ToString(converted_window_name);
382
} else {
383
LOG(WARN) << "Unable to get window name, IHTMLWindow2::get_name failed or returned a NULL value";
384
}
385
386
return name;
387
}
388
389
long Browser::GetWidth() {
390
LOG(TRACE) << "Entering Browser::GetWidth";
391
long width = 0;
392
this->browser_->get_Width(&width);
393
return width;
394
}
395
396
long Browser::GetHeight() {
397
LOG(TRACE) << "Entering Browser::GetHeight";
398
long height = 0;
399
this->browser_->get_Height(&height);
400
return height;
401
}
402
403
void Browser::SetWidth(long width) {
404
LOG(TRACE) << "Entering Browser::SetWidth";
405
this->browser_->put_Width(width);
406
}
407
408
void Browser::SetHeight(long height) {
409
LOG(TRACE) << "Entering Browser::SetHeight";
410
this->browser_->put_Height(height);
411
}
412
413
void Browser::AttachEvents() {
414
LOG(TRACE) << "Entering Browser::AttachEvents";
415
CComPtr<IUnknown> unknown;
416
this->browser_->QueryInterface<IUnknown>(&unknown);
417
HRESULT hr = this->DispEventAdvise(unknown);
418
}
419
420
void Browser::DetachEvents() {
421
LOG(TRACE) << "Entering Browser::DetachEvents";
422
CComPtr<IUnknown> unknown;
423
this->browser_->QueryInterface<IUnknown>(&unknown);
424
HRESULT hr = this->DispEventUnadvise(unknown);
425
}
426
427
void Browser::Close() {
428
LOG(TRACE) << "Entering Browser::Close";
429
if (this->is_edge_chromium()) {
430
// For Edge in IE Mode, cache the top-level hosting Chromium window
431
// handle so they can be properly closed on quit.
432
HWND top_level_window_handle = this->GetTopLevelWindowHandle();
433
::SendMessage(this->executor_handle(),
434
WD_ADD_CHROMIUM_WINDOW_HANDLE,
435
reinterpret_cast<WPARAM>(top_level_window_handle),
436
NULL);
437
}
438
439
this->is_explicit_close_requested_ = true;
440
this->set_is_closing(true);
441
// Closing the browser, so having focus on a frame doesn't
442
// make any sense.
443
this->SetFocusedFrameByElement(NULL);
444
445
HRESULT hr = S_OK;
446
hr = this->browser_->Stop();
447
hr = this->browser_->Quit();
448
449
if (FAILED(hr)) {
450
LOGHR(WARN, hr) << "Call to IWebBrowser2::Quit failed";
451
}
452
}
453
454
int Browser::NavigateToUrl(const std::string& url,
455
std::string* error_message) {
456
LOG(TRACE) << "Entring Browser::NavigateToUrl";
457
458
std::wstring wide_url = StringUtilities::ToWString(url);
459
CComVariant url_variant(wide_url.c_str());
460
CComVariant dummy;
461
462
HRESULT hr = this->browser_->Navigate2(&url_variant,
463
&dummy,
464
&dummy,
465
&dummy,
466
&dummy);
467
if (FAILED(hr)) {
468
LOGHR(WARN, hr) << "Call to IWebBrowser2::Navigate2 failed";
469
_com_error error(hr);
470
std::wstring formatted_message = StringUtilities::Format(
471
L"Received error: 0x%08x ['%s']",
472
hr,
473
error.ErrorMessage());
474
*error_message = StringUtilities::ToString(formatted_message);
475
return EUNHANDLEDERROR;
476
}
477
478
this->set_wait_required(true);
479
return WD_SUCCESS;
480
}
481
482
int Browser::NavigateBack() {
483
LOG(TRACE) << "Entering Browser::NavigateBack";
484
LPSTREAM stream;
485
HRESULT hr = ::CoMarshalInterThreadInterfaceInStream(IID_IWebBrowser2, this->browser_, &stream);
486
unsigned int thread_id = 0;
487
HANDLE thread_handle = reinterpret_cast<HANDLE>(_beginthreadex(NULL,
488
0,
489
&Browser::GoBackThreadProc,
490
(void *)stream,
491
0,
492
&thread_id));
493
if (thread_handle != NULL) {
494
::CloseHandle(thread_handle);
495
}
496
497
this->set_wait_required(true);
498
return WD_SUCCESS;
499
}
500
501
unsigned int WINAPI Browser::GoBackThreadProc(LPVOID param) {
502
HRESULT hr = ::CoInitialize(NULL);
503
IWebBrowser2* browser;
504
LPSTREAM message_payload = reinterpret_cast<LPSTREAM>(param);
505
hr = ::CoGetInterfaceAndReleaseStream(message_payload,
506
IID_IWebBrowser2,
507
reinterpret_cast<void**>(&browser));
508
if (browser != NULL) {
509
hr = browser->GoBack();
510
}
511
return 0;
512
}
513
514
int Browser::NavigateForward() {
515
LOG(TRACE) << "Entering Browser::NavigateForward";
516
LPSTREAM stream;
517
HRESULT hr = ::CoMarshalInterThreadInterfaceInStream(IID_IWebBrowser2, this->browser_, &stream);
518
unsigned int thread_id = 0;
519
HANDLE thread_handle = reinterpret_cast<HANDLE>(_beginthreadex(NULL,
520
0,
521
&Browser::GoForwardThreadProc,
522
(void *)stream,
523
0,
524
&thread_id));
525
if (thread_handle != NULL) {
526
::CloseHandle(thread_handle);
527
}
528
529
this->set_wait_required(true);
530
return WD_SUCCESS;
531
}
532
533
unsigned int WINAPI Browser::GoForwardThreadProc(LPVOID param) {
534
HRESULT hr = ::CoInitialize(NULL);
535
IWebBrowser2* browser;
536
LPSTREAM message_payload = reinterpret_cast<LPSTREAM>(param);
537
hr = ::CoGetInterfaceAndReleaseStream(message_payload,
538
IID_IWebBrowser2,
539
reinterpret_cast<void**>(&browser));
540
if (browser != NULL) {
541
hr = browser->GoForward();
542
}
543
return 0;
544
}
545
546
int Browser::Refresh() {
547
LOG(TRACE) << "Entering Browser::Refresh";
548
549
HRESULT hr = this->browser_->Refresh();
550
if (FAILED(hr)) {
551
LOGHR(WARN, hr) << "Call to IWebBrowser2::Refresh failed";
552
}
553
554
this->set_wait_required(true);
555
return WD_SUCCESS;
556
}
557
558
HWND Browser::GetTopLevelWindowHandle() {
559
LOG(TRACE) << "Entering Browser::GetTopLevelWindowHandle";
560
561
HWND top_level_window_handle = NULL;
562
HRESULT hr = this->browser_->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&top_level_window_handle));
563
if (FAILED(hr)) {
564
LOGHR(WARN, hr) << "Getting HWND property of IWebBrowser2 object failed";
565
}
566
567
return top_level_window_handle;
568
}
569
570
bool Browser::IsValidWindow() {
571
LOG(TRACE) << "Entering Browser::IsValidWindow";
572
// This is a no-op for this class. Full browser windows can properly notify
573
// of their window's validity by using the proper events.
574
return true;
575
}
576
577
bool Browser::IsBusy() {
578
VARIANT_BOOL is_busy(VARIANT_FALSE);
579
HRESULT hr = this->browser_->get_Busy(&is_busy);
580
return SUCCEEDED(hr) && is_busy == VARIANT_TRUE;
581
}
582
583
bool Browser::Wait(const std::string& page_load_strategy) {
584
LOG(TRACE) << "Entering Browser::Wait";
585
586
if (page_load_strategy == NONE_PAGE_LOAD_STRATEGY) {
587
LOG(DEBUG) << "Page load strategy is 'none'. Aborting wait.";
588
this->set_wait_required(false);
589
return true;
590
}
591
592
if (this->is_awaiting_new_process()) {
593
return false;
594
}
595
596
bool is_navigating = true;
597
598
LOG(DEBUG) << "Navigate Events Completed.";
599
this->is_navigation_started_ = false;
600
601
HWND dialog = this->GetActiveDialogWindowHandle();
602
if (dialog != NULL) {
603
LOG(DEBUG) << "Found alert. Aborting wait.";
604
this->set_wait_required(false);
605
return true;
606
}
607
608
// Navigate events completed. Waiting for browser.Busy != false...
609
is_navigating = this->is_navigation_started_;
610
if (is_navigating || (page_load_strategy == NORMAL_PAGE_LOAD_STRATEGY && this->IsBusy())) {
611
if (is_navigating) {
612
LOG(DEBUG) << "DocumentComplete event fired, indicating a new navigation.";
613
} else {
614
LOG(DEBUG) << "Browser busy property is true.";
615
}
616
return false;
617
}
618
619
READYSTATE expected_ready_state = READYSTATE_COMPLETE;
620
if (page_load_strategy == EAGER_PAGE_LOAD_STRATEGY) {
621
expected_ready_state = READYSTATE_INTERACTIVE;
622
}
623
624
// Waiting for browser.ReadyState >= expected ready state
625
is_navigating = this->is_navigation_started_;
626
READYSTATE ready_state;
627
HRESULT hr = this->browser_->get_ReadyState(&ready_state);
628
if (is_navigating || FAILED(hr) || ready_state < expected_ready_state) {
629
if (is_navigating) {
630
LOG(DEBUG) << "DocumentComplete event fired, indicating a new navigation.";
631
} else if (FAILED(hr)) {
632
LOGHR(DEBUG, hr) << "IWebBrowser2::get_ReadyState failed.";
633
} else {
634
LOG(DEBUG) << "Browser ReadyState is not at least '" << expected_ready_state << "'; it was " << ready_state;
635
}
636
return false;
637
}
638
639
// Waiting for document property != null...
640
is_navigating = this->is_navigation_started_;
641
CComPtr<IDispatch> document_dispatch;
642
hr = this->browser_->get_Document(&document_dispatch);
643
if (is_navigating || FAILED(hr) || !document_dispatch) {
644
if (is_navigating) {
645
LOG(DEBUG) << "DocumentComplete event fired, indicating a new navigation.";
646
} else if (FAILED(hr)) {
647
LOGHR(DEBUG, hr) << "IWebBrowser2::get_Document failed.";
648
} else {
649
LOG(DEBUG) << "Get Document failed; IWebBrowser2::get_Document did not return a valid IDispatch object.";
650
}
651
return false;
652
}
653
654
// Waiting for document to complete...
655
CComPtr<IHTMLDocument2> doc;
656
hr = document_dispatch->QueryInterface(&doc);
657
if (SUCCEEDED(hr)) {
658
LOG(DEBUG) << "Waiting for document to complete...";
659
is_navigating = this->IsDocumentNavigating(page_load_strategy, doc);
660
}
661
662
if (!is_navigating) {
663
LOG(DEBUG) << "Not in navigating state";
664
this->set_wait_required(false);
665
}
666
667
return !is_navigating;
668
}
669
670
bool Browser::IsDocumentNavigating(const std::string& page_load_strategy,
671
IHTMLDocument2* doc) {
672
LOG(TRACE) << "Entering Browser::IsDocumentNavigating";
673
674
bool is_navigating = true;
675
676
// Starting WaitForDocumentComplete()
677
is_navigating = this->is_navigation_started_;
678
CComBSTR ready_state_bstr;
679
HRESULT hr = doc->get_readyState(&ready_state_bstr);
680
if (FAILED(hr) || is_navigating) {
681
if (FAILED(hr)) {
682
LOGHR(DEBUG, hr) << "IHTMLDocument2::get_readyState failed.";
683
} else if (is_navigating) {
684
LOG(DEBUG) << "DocumentComplete event fired, indicating a new navigation.";
685
}
686
return true;
687
} else {
688
std::wstring ready_state = ready_state_bstr;
689
if ((ready_state == L"complete") ||
690
(page_load_strategy == EAGER_PAGE_LOAD_STRATEGY && ready_state == L"interactive")) {
691
is_navigating = false;
692
} else {
693
if (page_load_strategy == EAGER_PAGE_LOAD_STRATEGY) {
694
LOG(DEBUG) << "document.readyState is not 'complete' or 'interactive'; it was " << LOGWSTRING(ready_state);
695
} else {
696
LOG(DEBUG) << "document.readyState is not 'complete'; it was " << LOGWSTRING(ready_state);
697
}
698
return true;
699
}
700
}
701
702
// document.readyState == complete
703
is_navigating = this->is_navigation_started_;
704
CComPtr<IHTMLFramesCollection2> frames;
705
hr = doc->get_frames(&frames);
706
if (is_navigating || FAILED(hr)) {
707
LOG(DEBUG) << "Could not get frames, navigation has started or call to IHTMLDocument2::get_frames failed";
708
return true;
709
}
710
711
if (frames != NULL) {
712
long frame_count = 0;
713
hr = frames->get_length(&frame_count);
714
715
CComVariant index;
716
index.vt = VT_I4;
717
for (long i = 0; i < frame_count; ++i) {
718
// Waiting on each frame
719
index.lVal = i;
720
CComVariant result;
721
hr = frames->item(&index, &result);
722
if (FAILED(hr)) {
723
LOGHR(DEBUG, hr) << "Could not get frame item for index "
724
<< i
725
<< ", call to IHTMLFramesCollection2::item failed, frame/frameset is broken";
726
continue;
727
}
728
729
CComPtr<IHTMLWindow2> window;
730
result.pdispVal->QueryInterface<IHTMLWindow2>(&window);
731
if (!window) {
732
LOG(DEBUG) << "Could not get window for frame item with index "
733
<< i
734
<< ", cast to IHTMLWindow2 failed, frame is not an HTML frame";
735
continue;
736
}
737
738
CComPtr<IHTMLDocument2> frame_document;
739
bool is_valid_frame_document = this->GetDocumentFromWindow(window,
740
&frame_document);
741
742
is_navigating = this->is_navigation_started_;
743
if (is_navigating) {
744
break;
745
}
746
747
// Recursively call to wait for the frame document to complete
748
if (is_valid_frame_document) {
749
is_navigating = this->IsDocumentNavigating(page_load_strategy, frame_document);
750
if (is_navigating) {
751
break;
752
}
753
}
754
}
755
} else {
756
LOG(DEBUG) << "IHTMLDocument2.get_frames() returned empty collection";
757
}
758
return is_navigating;
759
}
760
761
bool Browser::GetDocumentFromWindow(IHTMLWindow2* window,
762
IHTMLDocument2** doc) {
763
LOG(TRACE) << "Entering Browser::GetDocumentFromWindow";
764
765
HRESULT hr = window->get_document(doc);
766
if (SUCCEEDED(hr)) {
767
return true;
768
}
769
770
if (hr == E_ACCESSDENIED) {
771
// Cross-domain documents may throw Access Denied. If so,
772
// get the document through the IWebBrowser2 interface.
773
CComPtr<IServiceProvider> service_provider;
774
hr = window->QueryInterface<IServiceProvider>(&service_provider);
775
if (FAILED(hr)) {
776
LOGHR(WARN, hr) << "Unable to get browser, call to IHTMLWindow2::QueryService failed for IServiceProvider";
777
return false;
778
}
779
780
CComPtr<IWebBrowser2> window_browser;
781
hr = service_provider->QueryService(IID_IWebBrowserApp, &window_browser);
782
if (FAILED(hr)) {
783
LOGHR(WARN, hr) << "Unable to get browser, call to IServiceProvider::QueryService failed for IID_IWebBrowserApp";
784
return false;
785
}
786
787
CComPtr<IDispatch> document_dispatch;
788
hr = window_browser->get_Document(&document_dispatch);
789
if (FAILED(hr) || hr == S_FALSE) {
790
LOGHR(WARN, hr) << "Unable to get document, call to IWebBrowser2::get_Document failed";
791
return false;
792
}
793
794
hr = document_dispatch->QueryInterface(doc);
795
if (FAILED(hr)) {
796
LOGHR(WARN, hr) << "Unable to query document, call to IDispatch::QueryInterface failed.";
797
return false;
798
}
799
800
return true;
801
} else {
802
LOGHR(WARN, hr) << "Unable to get main document, IHTMLWindow2::get_document returned other than E_ACCESSDENIED";
803
}
804
805
return false;
806
}
807
808
HWND Browser::GetBrowserWindowHandle() {
809
LOG(TRACE) << "Entering Browser::GetBrowserWindowHandle";
810
811
HWND hwnd = NULL;
812
CComPtr<IServiceProvider> service_provider;
813
HRESULT hr = this->browser_->QueryInterface(IID_IServiceProvider,
814
reinterpret_cast<void**>(&service_provider));
815
HWND hwnd_tmp = NULL;
816
this->browser_->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&hwnd_tmp));
817
if (SUCCEEDED(hr)) {
818
CComPtr<IOleWindow> window;
819
hr = service_provider->QueryService(SID_SShellBrowser,
820
IID_IOleWindow,
821
reinterpret_cast<void**>(&window));
822
if (SUCCEEDED(hr)) {
823
// This gets the TabWindowClass window in IE 7 and 8,
824
// and the top-level window frame in IE 6.
825
window->GetWindow(&hwnd);
826
} else {
827
LOGHR(WARN, hr) << "Unable to get window, call to IOleWindow::QueryService for SID_SShellBrowser failed";
828
}
829
} else {
830
LOGHR(WARN, hr) << "Unable to get service, call to IWebBrowser2::QueryInterface for IID_IServiceProvider failed";
831
}
832
833
return hwnd;
834
}
835
836
bool Browser::SetFullScreen(bool is_full_screen) {
837
VARIANT_BOOL full_screen_value = VARIANT_TRUE;
838
std::wstring full_screen_script = L"window.fullScreen = true;";
839
if (!is_full_screen) {
840
full_screen_value = VARIANT_FALSE;
841
full_screen_script = L"delete window.fullScreen;";
842
}
843
this->browser_->put_FullScreen(full_screen_value);
844
845
// IE does not support the W3C Fullscreen API (and likely never will).
846
// The prefixed version cannot be triggered via JavaScript outside of
847
// a user interaction, so we're going to cheat here and manually set
848
// the fullScreen property of the window object to the appropriate
849
// value. This may interfere with polyfills in use, and if that's
850
// the case, we'll revisit this hack.
851
CComPtr<IHTMLDocument2> doc;
852
this->GetDocument(true, &doc);
853
std::wstring script = ANONYMOUS_FUNCTION_START;
854
script += full_screen_script;
855
script += ANONYMOUS_FUNCTION_END;
856
Script script_wrapper(doc, script, 0);
857
script_wrapper.Execute();
858
return true;
859
}
860
861
bool Browser::IsFullScreen() {
862
VARIANT_BOOL is_full_screen = VARIANT_FALSE;
863
this->browser_->get_FullScreen(&is_full_screen);
864
return is_full_screen == VARIANT_TRUE;
865
}
866
867
HWND Browser::GetActiveDialogWindowHandle() {
868
LOG(TRACE) << "Entering Browser::GetActiveDialogWindowHandle";
869
870
HWND active_dialog_handle = NULL;
871
872
HWND content_window_handle = this->GetContentWindowHandle();
873
if (content_window_handle == NULL) {
874
return active_dialog_handle;
875
}
876
877
DWORD process_id = 0;
878
::GetWindowThreadProcessId(content_window_handle, &process_id);
879
if (process_id == 0) {
880
return active_dialog_handle;
881
}
882
883
ProcessWindowInfo process_win_info;
884
process_win_info.dwProcessId = process_id;
885
process_win_info.hwndBrowser = NULL;
886
::EnumWindows(&BrowserFactory::FindDialogWindowForProcess,
887
reinterpret_cast<LPARAM>(&process_win_info));
888
if (process_win_info.hwndBrowser != NULL) {
889
active_dialog_handle = process_win_info.hwndBrowser;
890
this->CheckDialogType(active_dialog_handle);
891
}
892
893
return active_dialog_handle;
894
}
895
896
void Browser::CheckDialogType(HWND dialog_window_handle) {
897
LOG(TRACE) << "Entering Browser::CheckDialogType";
898
899
std::vector<char> window_class_name(34);
900
if (GetClassNameA(dialog_window_handle, &window_class_name[0], 34)) {
901
if (strcmp(HTML_DIALOG_WINDOW_CLASS,
902
&window_class_name[0]) == 0) {
903
HWND content_window_handle = this->FindContentWindowHandle(dialog_window_handle);
904
::PostMessage(this->executor_handle(),
905
WD_NEW_HTML_DIALOG,
906
NULL,
907
reinterpret_cast<LPARAM>(content_window_handle));
908
}
909
}
910
}
911
912
} // namespace webdriver
913
914