Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/cpp/iedriver/Element.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
// Ignoring code analysis warnings for:
18
// "'argument n' might be '0': this does not adhere to the specification for
19
// the function 'IHTMLDocument4::createEventObject'", and "'argument n' might
20
// be null: this does not adhere to the specification for the function
21
// 'IHTMLDocument4::createEventObject'", and.
22
// IHTMLDocument4::createEventObject() should have its first argument set to
23
// NULL to create an empty event object, per documentation at:
24
// http://msdn.microsoft.com/en-us/library/aa752524(v=vs.85).aspx
25
#pragma warning (disable: 6309)
26
#pragma warning (disable: 6387)
27
28
#include "Element.h"
29
30
#include <algorithm>
31
32
#include "errorcodes.h"
33
#include "logging.h"
34
#include "json.h"
35
36
#include "Browser.h"
37
#include "Generated/atoms.h"
38
#include "Script.h"
39
#include "StringUtilities.h"
40
#include "VariantUtilities.h"
41
#include "WebDriverConstants.h"
42
43
namespace webdriver {
44
45
Element::Element(IHTMLElement* element, HWND containing_window_handle) {
46
LOG(TRACE) << "Entering Element::Element";
47
48
// NOTE: COM should be initialized on this thread, so we
49
// could use CoCreateGuid() and StringFromGUID2() instead.
50
UUID guid;
51
RPC_WSTR guid_string = NULL;
52
RPC_STATUS status = ::UuidCreate(&guid);
53
if (status != RPC_S_OK) {
54
// If we encounter an error, not bloody much we can do about it.
55
// Just log it and continue.
56
LOG(WARN) << "UuidCreate returned a status other then RPC_S_OK: " << status;
57
}
58
status = ::UuidToString(&guid, &guid_string);
59
if (status != RPC_S_OK) {
60
// If we encounter an error, not bloody much we can do about it.
61
// Just log it and continue.
62
LOG(WARN) << "UuidToString returned a status other then RPC_S_OK: " << status;
63
}
64
65
// RPC_WSTR is currently typedef'd in RpcDce.h (pulled in by rpc.h)
66
// as unsigned short*. It needs to be typedef'd as wchar_t*
67
wchar_t* cast_guid_string = reinterpret_cast<wchar_t*>(guid_string);
68
this->element_id_ = StringUtilities::ToString(cast_guid_string);
69
70
::RpcStringFree(&guid_string);
71
72
this->element_ = element;
73
this->containing_window_handle_ = containing_window_handle;
74
}
75
76
Element::Element(IHTMLElement* element, HWND containing_window_handle, const std::string& element_id) {
77
this->element_ = element;
78
this->element_id_ = element_id;
79
this->containing_window_handle_ = containing_window_handle;
80
}
81
82
Element::~Element(void) {
83
}
84
85
Json::Value Element::ConvertToJson() {
86
LOG(TRACE) << "Entering Element::ConvertToJson";
87
88
Json::Value json_wrapper;
89
json_wrapper[JSON_ELEMENT_PROPERTY_NAME] = this->element_id_;
90
91
return json_wrapper;
92
}
93
94
int Element::IsDisplayed(bool ignore_opacity, bool* result) {
95
LOG(TRACE) << "Entering Element::IsDisplayed";
96
97
int status_code = WD_SUCCESS;
98
99
// The atom is just the definition of an anonymous
100
// function: "function() {...}"; Wrap it in another function so we can
101
// invoke it with our arguments without polluting the current namespace.
102
std::wstring script_source(L"(function() { return (");
103
script_source += atoms::asString(atoms::IS_DISPLAYED);
104
script_source += L")})();";
105
106
CComPtr<IHTMLDocument2> doc;
107
this->GetContainingDocument(false, &doc);
108
// N.B., The second argument to the IsDisplayed atom is "ignoreOpacity".
109
Script script_wrapper(doc, script_source, 2);
110
script_wrapper.AddArgument(this->element_);
111
script_wrapper.AddArgument(ignore_opacity);
112
status_code = script_wrapper.Execute();
113
114
if (status_code == WD_SUCCESS) {
115
*result = script_wrapper.result().boolVal == VARIANT_TRUE;
116
} else {
117
LOG(WARN) << "Failed to determine is element displayed";
118
}
119
120
return status_code;
121
}
122
123
std::string Element::GetTagName() {
124
LOG(TRACE) << "Entering Element::GetTagName";
125
126
CComBSTR tag_name_bstr;
127
HRESULT hr = this->element_->get_tagName(&tag_name_bstr);
128
if (FAILED(hr)) {
129
LOGHR(WARN, hr) << "Failed calling IHTMLElement::get_tagName";
130
return "";
131
}
132
std::wstring converted_tag_name = tag_name_bstr;
133
std::string tag_name = StringUtilities::ToString(converted_tag_name);
134
std::transform(tag_name.begin(), tag_name.end(), tag_name.begin(), ::tolower);
135
return tag_name;
136
}
137
138
bool Element::IsEnabled() {
139
LOG(TRACE) << "Entering Element::IsEnabled";
140
141
bool result = false;
142
143
// The atom is just the definition of an anonymous
144
// function: "function() {...}"; Wrap it in another function so we can
145
// invoke it with our arguments without polluting the current namespace.
146
std::wstring script_source(L"(function() { return (");
147
script_source += atoms::asString(atoms::IS_ENABLED);
148
script_source += L")})();";
149
150
CComPtr<IHTMLDocument2> doc;
151
this->GetContainingDocument(false, &doc);
152
153
if (this->IsXmlDocument(doc)) {
154
return false;
155
}
156
157
Script script_wrapper(doc, script_source, 1);
158
script_wrapper.AddArgument(this->element_);
159
int status_code = script_wrapper.Execute();
160
161
if (status_code == WD_SUCCESS) {
162
result = script_wrapper.result().boolVal == VARIANT_TRUE;
163
} else {
164
LOG(WARN) << "Failed to determine is element enabled";
165
}
166
167
return result;
168
}
169
170
bool Element::IsXmlDocument(IHTMLDocument2* doc) {
171
LOG(TRACE) << "Entering Element::IsXmlDocument";
172
// If the document has an xmlVersion property, it can be either an XML
173
// document or an XHTML document. Otherwise, it's an HTML document.
174
CComPtr<IHTMLDocument7> xml_version_document;
175
HRESULT hr = doc->QueryInterface<IHTMLDocument7>(&xml_version_document);
176
if (SUCCEEDED(hr) && xml_version_document) {
177
CComBSTR xml_version = "";
178
hr = xml_version_document->get_xmlVersion(&xml_version);
179
if (SUCCEEDED(hr) && xml_version && xml_version != L"") {
180
// The document is either XML or XHTML, so to differentiate between
181
// the two cases, check for a doctype of "html". If we can't find
182
// a doctype property, or the doctype is anything other than "html",
183
// the document is an XML document.
184
CComPtr<IHTMLDocument5> doc_type_document;
185
hr = doc->QueryInterface<IHTMLDocument5>(&doc_type_document);
186
if (SUCCEEDED(hr) && doc_type_document) {
187
CComPtr<IHTMLDOMNode> doc_type_dom_node;
188
hr = doc_type_document->get_doctype(&doc_type_dom_node);
189
if (SUCCEEDED(hr) && doc_type_dom_node) {
190
CComPtr<IDOMDocumentType> doc_type;
191
hr = doc_type_dom_node->QueryInterface<IDOMDocumentType>(&doc_type);
192
if (SUCCEEDED(hr) && doc_type) {
193
CComBSTR type_name_bstr = L"";
194
hr = doc_type->get_name(&type_name_bstr);
195
type_name_bstr.ToLower();
196
std::wstring type_name(type_name_bstr);
197
LOG(INFO) << LOGWSTRING(type_name);
198
if (SUCCEEDED(hr) && type_name != L"html") {
199
return true;
200
}
201
}
202
} else {
203
return true;
204
}
205
}
206
}
207
}
208
return false;
209
}
210
211
bool Element::IsInteractable() {
212
LOG(TRACE) << "Entering Element::IsInteractable";
213
214
bool result = false;
215
216
// The atom is just the definition of an anonymous
217
// function: "function() {...}"; Wrap it in another function so we can
218
// invoke it with our arguments without polluting the current namespace.
219
std::wstring script_source(L"(function() { return (");
220
script_source += atoms::asString(atoms::IS_INTERACTABLE);
221
script_source += L")})();";
222
223
CComPtr<IHTMLDocument2> doc;
224
this->GetContainingDocument(false, &doc);
225
Script script_wrapper(doc, script_source, 1);
226
script_wrapper.AddArgument(this->element_);
227
int status_code = script_wrapper.Execute();
228
229
if (status_code == WD_SUCCESS) {
230
result = script_wrapper.result().boolVal == VARIANT_TRUE;
231
} else {
232
LOG(WARN) << "Failed to determine is element enabled";
233
}
234
235
return result;
236
}
237
238
bool Element::IsFocusable() {
239
LOG(TRACE) << "Entering Element::IsFocusable";
240
241
CComPtr<IHTMLBodyElement> body;
242
HRESULT hr = this->element_->QueryInterface<IHTMLBodyElement>(&body);
243
if (SUCCEEDED(hr) && body) {
244
// The <body> element is explicitly focusable.
245
return true;
246
}
247
248
CComPtr<IHTMLDocument2> doc;
249
this->GetContainingDocument(false, &doc);
250
251
CComPtr<IHTMLDocument3> document_element_doc;
252
hr = doc->QueryInterface<IHTMLDocument3>(&document_element_doc);
253
if (SUCCEEDED(hr) && document_element_doc) {
254
CComPtr<IHTMLElement> doc_element;
255
hr = document_element_doc->get_documentElement(&doc_element);
256
if (SUCCEEDED(hr) && doc_element && this->element_.IsEqualObject(doc_element)) {
257
// The document's documentElement is explicitly focusable.
258
return true;
259
}
260
}
261
return false;
262
}
263
264
bool Element::IsObscured(LocationInfo* click_location,
265
long* obscuring_element_index,
266
std::string* obscuring_element_description) {
267
CComPtr<ISVGElement> svg_element;
268
HRESULT hr = this->element_->QueryInterface<ISVGElement>(&svg_element);
269
if (SUCCEEDED(hr) && svg_element != NULL) {
270
// SVG elements can have complex paths making them non-hierarchical
271
// when drawn. We'll just assume the user knows what they're doing
272
// and bail on this test here.
273
return false;
274
}
275
276
// If an element has a style value where pointer-events is set to 'none',
277
// the element is "obscured" by definition, since any mouse interaction
278
// will not be handled by the element.
279
CComPtr<IHTMLCSSStyleDeclaration> computed_style;
280
if (this->GetComputedStyle(&computed_style)) {
281
CComBSTR pointer_events_value = L"";
282
hr = computed_style->get_pointerEvents(&pointer_events_value);
283
if (SUCCEEDED(hr) && pointer_events_value == L"none") {
284
return true;
285
}
286
}
287
288
// The element being obscured only makes sense within the context
289
// of its own document, even if it's not in the top-level document.
290
LocationInfo element_location = {};
291
int status_code = this->GetLocation(&element_location, nullptr);
292
*click_location = this->CalculateClickPoint(element_location, false);
293
long x = click_location->x;
294
long y = click_location->y;
295
296
bool is_inline = this->IsInline();
297
298
CComPtr<IHTMLDocument2> doc;
299
this->GetContainingDocument(false, &doc);
300
CComPtr<IHTMLElement> element_hit;
301
hr = doc->elementFromPoint(x, y, &element_hit);
302
if (SUCCEEDED(hr) && element_hit) {
303
if (element_.IsEqualObject(element_hit)) {
304
// Short circuit the use of elementsFromPoint if we don't
305
// have to use it.
306
return false;
307
} else {
308
// Short circuit in the case where this element is specifically
309
// an "inline" element (<label>, <span>, <a>, at present),
310
// and the top-most element as determined by elementFromPoint is
311
// a direct child of this element. This is to work around IE's bug
312
// in elementsFromPoint that does not return inline elements in the
313
// list of elements hit.
314
// N.B., this is a hack of the highest order, and there's every
315
// likelihood that some page somewhere will fail this check.
316
if (is_inline) {
317
CComPtr<IHTMLElement> element_hit_parent;
318
hr = element_hit->get_parentElement(&element_hit_parent);
319
CComBSTR element_hit_parent_tag;
320
element_hit_parent->get_tagName(&element_hit_parent_tag);
321
if (SUCCEEDED(hr) && element_hit_parent) {
322
if (this->element_.IsEqualObject(element_hit_parent)) {
323
return false;
324
}
325
}
326
}
327
}
328
}
329
330
bool has_shadow_root = this->HasShadowRoot();
331
CComPtr<IHTMLElement> shadow_root_parent;
332
if (has_shadow_root) {
333
// TODO: Walk up the DOM tree until we receive an ancestor that
334
// does not have a shadow root.
335
hr = this->element()->get_parentElement(&shadow_root_parent);
336
if (FAILED(hr)) {
337
LOGHR(WARN, hr) << "Element has shadow root, but cannot get parent";
338
}
339
}
340
341
CComPtr<IHTMLDocument8> elements_doc;
342
hr = doc.QueryInterface<IHTMLDocument8>(&elements_doc);
343
if (FAILED(hr)) {
344
// If we failed to QI for IHTMLDocument8, we can't easily determine if
345
// the element is obscured or not. We will assume we are not obscured
346
// and bail, even though that may not be the case.
347
LOGHR(WARN, hr) << "QueryInterface for IHTMLDocument8 failed";
348
return false;
349
}
350
351
bool is_obscured = false;
352
CComPtr<IHTMLDOMChildrenCollection> elements_hit;
353
hr = elements_doc->elementsFromPoint(static_cast<float>(x),
354
static_cast<float>(y),
355
&elements_hit);
356
if (SUCCEEDED(hr) && elements_hit != NULL) {
357
std::vector<std::string> element_descriptions;
358
long element_count;
359
elements_hit->get_length(&element_count);
360
for (long index = 0; index < element_count; ++index) {
361
CComPtr<IDispatch> dispatch_in_list;
362
elements_hit->item(index, &dispatch_in_list);
363
364
CComPtr<IHTMLElement> element_in_list;
365
hr = dispatch_in_list->QueryInterface<IHTMLElement>(&element_in_list);
366
bool are_equal = element_in_list.IsEqualObject(this->element_);
367
if (are_equal) {
368
break;
369
}
370
371
bool is_list_element_displayed;
372
Element list_element_wrapper(element_in_list,
373
this->containing_window_handle_);
374
status_code = list_element_wrapper.IsDisplayed(false,
375
&is_list_element_displayed);
376
if (is_list_element_displayed) {
377
if (has_shadow_root && shadow_root_parent) {
378
// Shadow DOM is problematic. Shadow DOM is only available in IE as a
379
// polyfill. If the element is part of a Shadow DOM (using a polyfill),
380
// elementsFromPoint will show the component elements, not necessarily
381
// the Web Component root element itself. If the direct parent of the
382
// Web Component host element is in this list, then it counts as a
383
// direct descendent, and won't be obscured.
384
bool is_shadow_root_parent = element_in_list.IsEqualObject(shadow_root_parent);
385
if (is_shadow_root_parent) {
386
break;
387
}
388
}
389
390
VARIANT_BOOL is_child;
391
hr = this->element_->contains(element_in_list, &is_child);
392
VARIANT_BOOL is_ancestor;
393
hr = element_in_list->contains(this->element_, &is_ancestor);
394
bool found_element_not_in_tree = is_child != VARIANT_TRUE &&
395
is_ancestor != VARIANT_TRUE;
396
if (found_element_not_in_tree) {
397
CComPtr<IHTMLFrameBase> frame_element;
398
hr = element_in_list->QueryInterface<IHTMLFrameBase>(&frame_element);
399
if (SUCCEEDED(hr) && frame_element) {
400
// Candidate element is a <frame> or <iframe>, meaning it must
401
// be a different document tree, which implies that it cannot
402
// be obscuring the element we are attempting to click on.
403
continue;
404
}
405
406
CComPtr<IHTMLCSSStyleDeclaration> list_element_computed_style;
407
if (list_element_wrapper.GetComputedStyle(&list_element_computed_style)) {
408
CComBSTR list_element_pointer_events_value = L"";
409
hr = list_element_computed_style->get_pointerEvents(&list_element_pointer_events_value);
410
if (SUCCEEDED(hr) && list_element_pointer_events_value != L"none") {
411
// If the element has a pointer-events value set to 'none', it
412
// may be technically obscuring this element, but manipulating
413
// it with the pointer device has no effect, so it is effectively
414
// not obscuring this element.
415
is_obscured = true;
416
}
417
} else {
418
// We were unable to retrieve the computed style, so we must assume
419
// the other element is obscuring this one.
420
is_obscured = true;
421
}
422
} else {
423
// Repeating the immediate-child-of-inline-element hack from above for
424
// elements found in the list.
425
if (is_inline) {
426
CComPtr<IHTMLElement> list_element_parent;
427
hr = element_in_list->get_parentElement(&list_element_parent);
428
if (SUCCEEDED(hr) && list_element_parent) {
429
if (this->element_.IsEqualObject(list_element_parent)) {
430
break;
431
}
432
}
433
}
434
}
435
if (is_obscured) {
436
// Return the top-most element in the event we find an obscuring
437
// element in the tree between this element and the top-most one.
438
// Note that since it's the top-most element, it will have no
439
// descendants, so its outerHTML property will contain only itself.
440
std::string outer_html = this->GetElementHtmlDescription(element_in_list);
441
*obscuring_element_index = index;
442
*obscuring_element_description = outer_html;
443
break;
444
}
445
}
446
}
447
}
448
449
return is_obscured;
450
}
451
452
std::string Element::GetElementHtmlDescription(IHTMLElement* element) {
453
CComBSTR outer_html_bstr;
454
HRESULT hr = element->get_outerHTML(&outer_html_bstr);
455
std::wstring outer_html = outer_html_bstr;
456
size_t bracket_pos = outer_html.find(L'>');
457
if (bracket_pos != std::wstring::npos) {
458
outer_html = outer_html.substr(0, bracket_pos + 1);
459
}
460
return StringUtilities::ToString(outer_html);
461
}
462
463
bool Element::HasShadowRoot() {
464
std::wstring script_source(ANONYMOUS_FUNCTION_START);
465
script_source += L"return (function() { if (arguments[0].shadowRoot && arguments[0].shadowRoot !== null) { return true; } return false; })";
466
script_source += ANONYMOUS_FUNCTION_END;
467
468
CComPtr<IHTMLDocument2> doc;
469
this->GetContainingDocument(false, &doc);
470
Script script_wrapper(doc, script_source, 1);
471
script_wrapper.AddArgument(this->element_);
472
int status_code = script_wrapper.Execute();
473
if (status_code == WD_SUCCESS) {
474
if (script_wrapper.ResultIsBoolean()) {
475
return script_wrapper.result().boolVal == VARIANT_TRUE;
476
}
477
}
478
return false;
479
}
480
481
bool Element::GetComputedStyle(IHTMLCSSStyleDeclaration** computed_style) {
482
HRESULT hr = S_OK;
483
CComPtr<IHTMLDocument2> doc;
484
int status_code = this->GetContainingDocument(false, &doc);
485
if (status_code == WD_SUCCESS) {
486
CComPtr<IHTMLWindow2> window;
487
hr = doc->get_parentWindow(&window);
488
if (SUCCEEDED(hr) && window) {
489
CComPtr<IHTMLWindow7> style_window;
490
hr = window->QueryInterface<IHTMLWindow7>(&style_window);
491
if (SUCCEEDED(hr) && style_window) {
492
CComPtr<IHTMLDOMNode> node;
493
hr = this->element_->QueryInterface<IHTMLDOMNode>(&node);
494
if (SUCCEEDED(hr) && node) {
495
hr = style_window->getComputedStyle(node, NULL, computed_style);
496
if (SUCCEEDED(hr) && computed_style) {
497
return true;
498
}
499
}
500
}
501
}
502
}
503
return false;
504
}
505
506
bool Element::IsEditable() {
507
LOG(TRACE) << "Entering Element::IsEditable";
508
509
bool result = false;
510
511
// The atom is just the definition of an anonymous
512
// function: "function() {...}"; Wrap it in another function so we can
513
// invoke it with our arguments without polluting the current namespace.
514
std::wstring script_source(L"(function() { return (");
515
script_source += atoms::asString(atoms::IS_EDITABLE);
516
script_source += L")})();";
517
518
CComPtr<IHTMLDocument2> doc;
519
this->GetContainingDocument(false, &doc);
520
Script script_wrapper(doc, script_source, 1);
521
script_wrapper.AddArgument(this->element_);
522
int status_code = script_wrapper.Execute();
523
524
if (status_code == WD_SUCCESS) {
525
result = script_wrapper.result().boolVal == VARIANT_TRUE;
526
} else {
527
LOG(WARN) << "Failed to determine is element enabled";
528
}
529
530
return result;
531
}
532
533
int Element::GetClickLocation(const ElementScrollBehavior scroll_behavior,
534
LocationInfo* element_location,
535
LocationInfo* click_location) {
536
LOG(TRACE) << "Entering Element::GetClickLocation";
537
538
bool displayed;
539
int status_code = this->IsDisplayed(true, &displayed);
540
if (status_code != WD_SUCCESS) {
541
LOG(WARN) << "Unable to determine element is displayed";
542
return status_code;
543
}
544
545
if (!displayed) {
546
LOG(WARN) << "Element is not displayed";
547
return EELEMENTNOTDISPLAYED;
548
}
549
550
std::vector<LocationInfo> frame_locations;
551
status_code = this->GetLocationOnceScrolledIntoView(scroll_behavior,
552
element_location,
553
&frame_locations);
554
555
if (status_code == WD_SUCCESS) {
556
bool document_contains_frames = frame_locations.size() != 0;
557
*click_location = CalculateClickPoint(*element_location,
558
document_contains_frames);
559
}
560
return status_code;
561
}
562
563
int Element::GetStaticClickLocation(LocationInfo* click_location) {
564
std::vector<LocationInfo> frame_locations;
565
LocationInfo element_location = {};
566
int result = this->GetLocation(&element_location, &frame_locations);
567
bool document_contains_frames = frame_locations.size() != 0;
568
*click_location = this->CalculateClickPoint(element_location, document_contains_frames);
569
return result;
570
}
571
572
int Element::GetAttributeValue(const std::string& attribute_name,
573
VARIANT* attribute_value) {
574
LOG(TRACE) << "Entering Element::GetAttributeValue";
575
576
std::wstring wide_attribute_name = StringUtilities::ToWString(attribute_name);
577
int status_code = WD_SUCCESS;
578
579
// The atom is just the definition of an anonymous
580
// function: "function() {...}"; Wrap it in another function so we can
581
// invoke it with our arguments without polluting the current namespace.
582
std::wstring script_source(L"(function() { return (");
583
script_source += atoms::asString(atoms::GET_ATTRIBUTE);
584
script_source += L")})();";
585
586
CComPtr<IHTMLDocument2> doc;
587
this->GetContainingDocument(false, &doc);
588
Script script_wrapper(doc, script_source, 2);
589
script_wrapper.AddArgument(this->element_);
590
script_wrapper.AddArgument(wide_attribute_name);
591
status_code = script_wrapper.Execute();
592
593
if (status_code == WD_SUCCESS) {
594
::VariantCopy(attribute_value, &script_wrapper.result());
595
} else {
596
LOG(WARN) << "Failed to determine element attribute";
597
}
598
599
return WD_SUCCESS;
600
}
601
602
int Element::GetPropertyValue(const std::string& property_name,
603
VARIANT* property_value) {
604
LOG(TRACE) << "Entering Element::GetPropertyValue";
605
606
std::wstring wide_property_name = StringUtilities::ToWString(property_name);
607
int status_code = WD_SUCCESS;
608
609
LPOLESTR property_name_pointer = reinterpret_cast<LPOLESTR>(const_cast<wchar_t*>(wide_property_name.data()));
610
DISPID dispid_property;
611
HRESULT hr = this->element_->GetIDsOfNames(IID_NULL,
612
&property_name_pointer,
613
1,
614
LOCALE_USER_DEFAULT,
615
&dispid_property);
616
if (FAILED(hr)) {
617
LOGHR(WARN, hr) << "Unable to get dispatch ID (dispid) for property "
618
<< property_name;
619
property_value->vt = VT_EMPTY;
620
return WD_SUCCESS;
621
}
622
623
// get the value of eval result
624
DISPPARAMS no_args_dispatch_parameters = { 0 };
625
hr = this->element_->Invoke(dispid_property,
626
IID_NULL,
627
LOCALE_USER_DEFAULT,
628
DISPATCH_PROPERTYGET,
629
&no_args_dispatch_parameters,
630
property_value,
631
NULL,
632
NULL);
633
if (FAILED(hr)) {
634
LOGHR(WARN, hr) << "Unable to get result for property "
635
<< property_name;
636
property_value->vt = VT_EMPTY;
637
return WD_SUCCESS;
638
}
639
640
return WD_SUCCESS;
641
}
642
643
int Element::GetCssPropertyValue(const std::string& property_name,
644
std::string* property_value) {
645
LOG(TRACE) << "Entering Element::GetCssPropertyValue";
646
647
int status_code = WD_SUCCESS;
648
CComPtr<IHTMLDocument2> doc;
649
this->GetContainingDocument(false, &doc);
650
if (this->IsXmlDocument(doc)) {
651
*property_value = "";
652
return status_code;
653
}
654
655
// The atom is just the definition of an anonymous
656
// function: "function() {...}"; Wrap it in another function so we can
657
// invoke it with our arguments without polluting the current namespace.
658
std::wstring script_source = L"(function() { return (";
659
script_source += atoms::asString(atoms::GET_EFFECTIVE_STYLE);
660
script_source += L")})();";
661
662
Script script_wrapper(doc, script_source, 2);
663
script_wrapper.AddArgument(this->element_);
664
script_wrapper.AddArgument(property_name);
665
status_code = script_wrapper.Execute();
666
667
if (status_code == WD_SUCCESS) {
668
std::wstring raw_value = L"";
669
if (script_wrapper.ResultIsString()) {
670
raw_value.assign(script_wrapper.result().bstrVal);
671
} else if (script_wrapper.ResultIsInteger()) {
672
long int_value = script_wrapper.result().lVal;
673
raw_value = std::to_wstring(int_value);
674
} else if (script_wrapper.ResultIsDouble()) {
675
double dbl_value = script_wrapper.result().dblVal;
676
raw_value = std::to_wstring(dbl_value);
677
} else if (script_wrapper.ResultIsBoolean()) {
678
if (script_wrapper.result().boolVal == VARIANT_TRUE) {
679
raw_value = L"true";
680
} else {
681
raw_value = L"false";
682
}
683
}
684
std::string value = StringUtilities::ToString(raw_value);
685
std::transform(value.begin(),
686
value.end(),
687
value.begin(),
688
tolower);
689
*property_value = value;
690
} else {
691
LOG(WARN) << "Failed to get value of CSS property";
692
}
693
return status_code;
694
}
695
696
int Element::GetLocationOnceScrolledIntoView(const ElementScrollBehavior scroll,
697
LocationInfo* location,
698
std::vector<LocationInfo>* frame_locations) {
699
LOG(TRACE) << "Entering Element::GetLocationOnceScrolledIntoView";
700
701
int status_code = WD_SUCCESS;
702
CComPtr<IHTMLDOMNode2> node;
703
HRESULT hr = this->element_->QueryInterface(&node);
704
705
if (FAILED(hr)) {
706
LOGHR(WARN, hr) << "Cannot cast html element to node, QI on IHTMLElement for IHTMLDOMNode2 failed";
707
return ENOSUCHELEMENT;
708
}
709
710
LocationInfo element_location = {};
711
int result = this->GetLocation(&element_location, frame_locations);
712
bool document_contains_frames = frame_locations->size() != 0;
713
LocationInfo click_location = this->CalculateClickPoint(element_location, document_contains_frames);
714
715
if (result != WD_SUCCESS ||
716
!this->IsLocationInViewPort(click_location, document_contains_frames) ||
717
this->IsHiddenByOverflow(element_location, click_location) ||
718
!this->IsLocationVisibleInFrames(click_location, *frame_locations)) {
719
// Scroll the element into view
720
LOG(DEBUG) << "Will need to scroll element into view";
721
CComVariant scroll_behavior = VARIANT_TRUE;
722
if (scroll == BOTTOM) {
723
scroll_behavior = VARIANT_FALSE;
724
}
725
hr = this->element_->scrollIntoView(scroll_behavior);
726
if (FAILED(hr)) {
727
LOGHR(WARN, hr) << "Cannot scroll element into view, IHTMLElement::scrollIntoView failed";
728
return EOBSOLETEELEMENT;
729
}
730
731
std::vector<LocationInfo> scrolled_frame_locations;
732
result = this->GetLocation(&element_location, &scrolled_frame_locations);
733
if (result != WD_SUCCESS) {
734
LOG(WARN) << "Unable to get location of scrolled to element";
735
return result;
736
}
737
738
click_location = this->CalculateClickPoint(element_location, document_contains_frames);
739
if (!this->IsLocationInViewPort(click_location, document_contains_frames)) {
740
LOG(WARN) << "Scrolled element is not in view";
741
status_code = EELEMENTCLICKPOINTNOTSCROLLED;
742
}
743
744
// TODO: Handle the case where the element's click point is in
745
// the view port but hidden by the overflow of a parent element.
746
// That could would look something like the following:
747
// if (this->IsHiddenByOverflow(element_location, click_location)) {
748
// if (!this->IsEntirelyHiddenByOverflow()) {
749
// this->ScrollWithinOverflow(element_location);
750
// }
751
// status_code = EELEMENTCLICKPOINTNOTSCROLLED;
752
// }
753
}
754
755
LOG(DEBUG) << "(x, y, w, h): "
756
<< element_location.x << ", "
757
<< element_location.y << ", "
758
<< element_location.width << ", "
759
<< element_location.height;
760
761
// At this point, we know the element is displayed according to its
762
// style attributes, and we've made a best effort at scrolling it so
763
// that it's completely within the viewport. We will always return
764
// the coordinates of the element, even if the scrolling is unsuccessful.
765
// However, we will still return the "element not displayed" status code
766
// if the click point has not been scrolled to the viewport.
767
location->x = element_location.x;
768
location->y = element_location.y;
769
location->width = element_location.width;
770
location->height = element_location.height;
771
772
return status_code;
773
}
774
775
bool Element::IsHiddenByOverflow(const LocationInfo element_location,
776
const LocationInfo click_location) {
777
LOG(TRACE) << "Entering Element::IsHiddenByOverflow";
778
779
bool is_overflow = false;
780
781
int x_offset = click_location.x - element_location.x;
782
int y_offset = click_location.y - element_location.y;
783
784
std::wstring script_source(L"(function() { return (");
785
script_source += atoms::asString(atoms::IS_OFFSET_IN_PARENT_OVERFLOW);
786
script_source += L")})();";
787
788
CComPtr<IHTMLDocument2> doc;
789
this->GetContainingDocument(false, &doc);
790
Script script_wrapper(doc, script_source, 3);
791
script_wrapper.AddArgument(this->element_);
792
script_wrapper.AddArgument(x_offset);
793
script_wrapper.AddArgument(y_offset);
794
int status_code = script_wrapper.Execute();
795
if (status_code == WD_SUCCESS) {
796
std::wstring raw_overflow_state(script_wrapper.result().bstrVal);
797
std::string overflow_state = StringUtilities::ToString(raw_overflow_state);
798
is_overflow = (overflow_state == "scroll");
799
} else {
800
LOG(WARN) << "Unable to determine is element hidden by overflow";
801
}
802
803
return is_overflow;
804
}
805
806
bool Element::IsEntirelyHiddenByOverflow() {
807
LOG(TRACE) << "Entering Element::IsEntirelyHiddenByOverflow";
808
809
bool is_overflow = false;
810
811
std::wstring script_source(L"(function() { return (");
812
script_source += atoms::asString(atoms::IS_ELEMENT_IN_PARENT_OVERFLOW);
813
script_source += L")})();";
814
815
CComPtr<IHTMLDocument2> doc;
816
this->GetContainingDocument(false, &doc);
817
Script script_wrapper(doc, script_source, 1);
818
script_wrapper.AddArgument(this->element_);
819
int status_code = script_wrapper.Execute();
820
if (status_code == WD_SUCCESS) {
821
std::wstring raw_overflow_state(script_wrapper.result().bstrVal);
822
std::string overflow_state = StringUtilities::ToString(raw_overflow_state);
823
is_overflow = (overflow_state == "scroll");
824
} else {
825
LOG(WARN) << "Unable to determine is element hidden by overflow";
826
}
827
828
return is_overflow;
829
}
830
831
bool Element::ScrollWithinOverflow(const LocationInfo element_location) {
832
RECT element_rect;
833
element_rect.left = element_location.x;
834
element_rect.top = element_location.y;
835
element_rect.right = element_location.x + element_location.width;
836
element_rect.bottom = element_location.y + element_location.height;
837
838
CComPtr<IHTMLElement> parent_element;
839
this->element_->get_parentElement(&parent_element);
840
while (parent_element != NULL) {
841
CComPtr<IHTMLElement2> el2;
842
parent_element->QueryInterface<IHTMLElement2>(&el2);
843
CComPtr<IHTMLRect> parent_bounding_rect;
844
el2->getBoundingClientRect(&parent_bounding_rect);
845
RECT parent_rect;
846
parent_bounding_rect->get_left(&parent_rect.left);
847
parent_bounding_rect->get_top(&parent_rect.top);
848
parent_bounding_rect->get_right(&parent_rect.right);
849
parent_bounding_rect->get_bottom(&parent_rect.bottom);
850
RECT intersection;
851
if (::IntersectRect(&intersection, &element_rect, &parent_rect)) {
852
if (::EqualRect(&intersection, &element_rect)) {
853
CComPtr<IHTMLElement> next_ancestor;
854
// The entire element is visible within this ancestor.
855
// Need to proceed to the next ancestor in the tree.
856
parent_element->get_parentElement(&next_ancestor);
857
parent_element.Release();
858
parent_element = next_ancestor;
859
} else {
860
// We have the intersecting rect, so adjust the location
861
long intersection_vert_center = intersection.top + ((intersection.bottom - intersection.top) / 2);
862
long intersection_horiz_center = intersection.left + ((intersection.right - intersection.left) / 2);
863
864
long offset_top = 0;
865
element_->get_offsetTop(&offset_top);
866
offset_top += element_location.height / 2;
867
868
long offset_left = 0;
869
element_->get_offsetLeft(&offset_left);
870
offset_left += element_location.width / 2;
871
872
el2->put_scrollTop(offset_top - intersection_vert_center);
873
el2->put_scrollLeft(offset_left - intersection_horiz_center);
874
return true;
875
}
876
} else {
877
// the rects don't intersect, so something went wrong.
878
break;
879
}
880
}
881
return false;
882
}
883
bool Element::IsLocationVisibleInFrames(const LocationInfo location,
884
const std::vector<LocationInfo> frame_locations) {
885
std::vector<LocationInfo>::const_iterator iterator = frame_locations.begin();
886
for (; iterator != frame_locations.end(); ++iterator) {
887
if (location.x < iterator->x ||
888
location.y < iterator->y ||
889
location.x > iterator->x + iterator->width ||
890
location.y > iterator->y + iterator->height) {
891
return false;
892
}
893
}
894
return true;
895
}
896
897
bool Element::IsSelected() {
898
LOG(TRACE) << "Entering Element::IsSelected";
899
900
bool selected(false);
901
// The atom is just the definition of an anonymous
902
// function: "function() {...}"; Wrap it in another function so we can
903
// invoke it with our arguments without polluting the current namespace.
904
std::wstring script_source(L"(function() { return (");
905
script_source += atoms::asString(atoms::IS_SELECTED);
906
script_source += L")})();";
907
908
CComPtr<IHTMLDocument2> doc;
909
this->GetContainingDocument(false, &doc);
910
Script script_wrapper(doc, script_source, 1);
911
script_wrapper.AddArgument(this->element_);
912
int status_code = script_wrapper.Execute();
913
914
if (status_code == WD_SUCCESS && script_wrapper.ResultIsBoolean()) {
915
selected = script_wrapper.result().boolVal == VARIANT_TRUE;
916
} else {
917
LOG(WARN) << "Unable to determine is element selected";
918
}
919
920
return selected;
921
}
922
923
bool Element::IsImageMap(LocationInfo* location) {
924
CComPtr<IHTMLElement> map_element;
925
CComPtr<IHTMLAreaElement> area_element;
926
CComPtr<IHTMLMapElement> map_element_candidate;
927
this->element_->QueryInterface<IHTMLMapElement>(&map_element_candidate);
928
if (map_element_candidate == NULL) {
929
this->element_->QueryInterface<IHTMLAreaElement>(&area_element);
930
if (area_element) {
931
this->element_->get_parentElement(&map_element);
932
if (map_element) {
933
map_element->QueryInterface<IHTMLMapElement>(&map_element_candidate);
934
}
935
}
936
}
937
938
if (map_element_candidate && map_element) {
939
CComBSTR name_bstr;
940
map_element_candidate->get_name(&name_bstr);
941
CComBSTR img_selector = L"*[usemap='#";
942
img_selector.Append(name_bstr);
943
img_selector.Append(L"']");
944
945
CComPtr<IDispatch> doc_dispatch;
946
map_element->get_document(&doc_dispatch);
947
948
CComPtr<IDocumentSelector> doc;
949
doc_dispatch->QueryInterface<IDocumentSelector>(&doc);
950
if (doc) {
951
CComPtr<IHTMLElement> img_element;
952
doc->querySelector(img_selector, &img_element);
953
if (img_element) {
954
CComPtr<IHTMLElement2> rect_element;
955
img_element->QueryInterface<IHTMLElement2>(&rect_element);
956
if (rect_element) {
957
CComPtr<IHTMLRect> rect;
958
rect_element->getBoundingClientRect(&rect);
959
RECT img_rect;
960
rect->get_left(&img_rect.left);
961
rect->get_top(&img_rect.top);
962
rect->get_right(&img_rect.right);
963
rect->get_bottom(&img_rect.bottom);
964
965
CComBSTR shape;
966
area_element->get_shape(&shape);
967
shape.ToLower();
968
if (shape == L"default") {
969
location->x = img_rect.left;
970
location->y = img_rect.top;
971
location->width = img_rect.right - img_rect.left;
972
location->height = img_rect.bottom - img_rect.top;
973
return true;
974
}
975
976
CComBSTR coords_bstr;
977
area_element->get_coords(&coords_bstr);
978
std::wstring coords(coords_bstr);
979
std::vector<std::wstring> individual;
980
StringUtilities::Split(coords, L",", &individual);
981
RECT area_rect = { 0, 0, 0, 0 };
982
if (shape == L"rect" && individual.size() == 4) {
983
area_rect.left = std::stol(individual.at(0).c_str(), 0, 10);
984
area_rect.top = std::stol(individual.at(1).c_str(), 0, 10);
985
area_rect.right = std::stol(individual.at(2).c_str(), 0, 10);
986
area_rect.bottom = std::stol(individual.at(3).c_str(), 0, 10);
987
}
988
else if ((shape == L"circle" || shape == "circ") && individual.size() == 3) {
989
long center_x = std::stol(individual.at(0), 0, 10);
990
long center_y = std::stol(individual.at(1), 0, 10);
991
long radius = std::stol(individual.at(2), 0, 10);
992
area_rect.left = center_x - radius;
993
area_rect.top = center_y - radius;
994
area_rect.right = center_x + radius;
995
area_rect.bottom = center_y + radius;
996
}
997
else if ((shape == L"poly" || shape == L"polygon") && individual.size() > 2) {
998
long min_x = std::stol(individual.at(0), 0, 10);
999
long min_y = std::stol(individual.at(1), 0, 10);
1000
long max_x = min_x;
1001
long max_y = min_y;
1002
for (size_t i = 2; i + 1 < individual.size(); i += 2) {
1003
long next_x = std::stol(individual.at(i), 0, 10);
1004
long next_y = std::stol(individual.at(i + 1), 0, 10);
1005
min_x = min(min_x, next_x);
1006
max_x = max(max_x, next_x);
1007
min_y = min(min_y, next_y);
1008
max_y = max(max_y, next_y);
1009
}
1010
area_rect.left = min_x;
1011
area_rect.bottom = min_y;
1012
area_rect.right = max_x;
1013
area_rect.bottom = max_y;
1014
}
1015
else {
1016
// Invalid shape value or coordinate values. Not modifying location.
1017
return false;
1018
}
1019
1020
long img_width = img_rect.right - img_rect.left;
1021
long img_height = img_rect.bottom - img_rect.top;
1022
long area_width = area_rect.right - area_rect.left;
1023
long area_height = area_rect.bottom - area_rect.top;
1024
location->x = img_rect.left + min(max(area_rect.left, 0), img_width);
1025
location->y = img_rect.top + min(max(area_rect.top, 0), img_height);
1026
location->width = min(area_width, img_width - location->x);
1027
location->height = min(area_height, img_height - location->y);
1028
return true;
1029
}
1030
}
1031
}
1032
}
1033
return false;
1034
}
1035
1036
int Element::GetLocation(LocationInfo* location,
1037
std::vector<LocationInfo>* frame_locations) {
1038
LOG(TRACE) << "Entering Element::GetLocation";
1039
1040
bool has_absolute_position_ready_to_return = false;
1041
1042
CComPtr<IHTMLElement2> element2;
1043
HRESULT hr = this->element_->QueryInterface(&element2);
1044
if (FAILED(hr)) {
1045
LOGHR(WARN, hr) << "Unable to cast element to IHTMLElement2";
1046
return EOBSOLETEELEMENT;
1047
}
1048
1049
long top = 0, bottom = 0, left = 0, right = 0;
1050
LocationInfo map_location = { 0, 0, 0, 0 };
1051
if (this->IsImageMap(&map_location)) {
1052
left = map_location.x;
1053
top = map_location.y;
1054
right = map_location.x + map_location.width;
1055
bottom = map_location.y + map_location.height;
1056
} else {
1057
// If this element is inline, we need to check whether we should
1058
// use getBoundingClientRect() or the first non-zero-sized rect returned
1059
// by getClientRects(). If the element is not inline, we can use
1060
// getBoundingClientRect() directly.
1061
CComPtr<IHTMLRect> rect;
1062
if (this->IsInline()) {
1063
CComPtr<IHTMLRectCollection> rects;
1064
hr = element2->getClientRects(&rects);
1065
long rect_count;
1066
rects->get_length(&rect_count);
1067
if (rect_count > 1) {
1068
LOG(DEBUG) << "Element is inline with multiple client rects, finding first non-zero sized client rect";
1069
for (long i = 0; i < rect_count; ++i) {
1070
CComVariant index(i);
1071
CComVariant rect_variant;
1072
hr = rects->item(&index, &rect_variant);
1073
if (SUCCEEDED(hr) && rect_variant.pdispVal) {
1074
CComPtr<IHTMLRect> qi_rect;
1075
rect_variant.pdispVal->QueryInterface<IHTMLRect>(&qi_rect);
1076
if (qi_rect) {
1077
rect = qi_rect;
1078
if (RectHasNonZeroDimensions(rect)) {
1079
// IE returns absolute positions in the page, rather than frame- and scroll-bound
1080
// positions, for clientRects (as opposed to boundingClientRects).
1081
has_absolute_position_ready_to_return = true;
1082
break;
1083
}
1084
}
1085
}
1086
}
1087
}
1088
else {
1089
LOG(DEBUG) << "Element is inline with one client rect, using IHTMLElement2::getBoundingClientRect";
1090
hr = element2->getBoundingClientRect(&rect);
1091
}
1092
}
1093
else {
1094
LOG(DEBUG) << "Element is a block element, using IHTMLElement2::getBoundingClientRect";
1095
hr = element2->getBoundingClientRect(&rect);
1096
if (this->HasFirstChildTextNodeOfMultipleChildren()) {
1097
LOG(DEBUG) << "Element has multiple children, but the first child is a text node, using text node boundaries";
1098
// Note that since subsequent statements in this method use the HTMLRect
1099
// object, we will update that object with the values of the text node.
1100
LocationInfo text_node_location;
1101
this->GetTextBoundaries(&text_node_location);
1102
rect->put_left(text_node_location.x);
1103
rect->put_top(text_node_location.y);
1104
rect->put_right(text_node_location.x + text_node_location.width);
1105
rect->put_bottom(text_node_location.y + text_node_location.height);
1106
}
1107
}
1108
if (FAILED(hr)) {
1109
LOGHR(WARN, hr) << "Cannot figure out where the element is on screen, client rect retrieval failed";
1110
return EUNHANDLEDERROR;
1111
}
1112
1113
// If the rect of the element has zero width and height, check its
1114
// children to see if any of them have width and height, in which
1115
// case, this element will be visible.
1116
if (!RectHasNonZeroDimensions(rect)) {
1117
LOG(DEBUG) << "Element has client rect with zero dimension, checking children for non-zero dimension client rects";
1118
CComPtr<IHTMLDOMNode> node;
1119
element2->QueryInterface(&node);
1120
CComPtr<IDispatch> children_dispatch;
1121
node->get_childNodes(&children_dispatch);
1122
CComPtr<IHTMLDOMChildrenCollection> children;
1123
children_dispatch->QueryInterface<IHTMLDOMChildrenCollection>(&children);
1124
if (!!children) {
1125
long children_count = 0;
1126
children->get_length(&children_count);
1127
for (long i = 0; i < children_count; ++i) {
1128
CComPtr<IDispatch> child_dispatch;
1129
children->item(i, &child_dispatch);
1130
CComPtr<IHTMLElement> child;
1131
child_dispatch->QueryInterface(&child);
1132
if (child != NULL) {
1133
int result = WD_SUCCESS;
1134
Element child_element(child, this->containing_window_handle_);
1135
if (frame_locations == nullptr) {
1136
result = child_element.GetLocation(location, nullptr);
1137
}
1138
else {
1139
std::vector<LocationInfo> child_frame_locations;
1140
result = child_element.GetLocation(location, &child_frame_locations);
1141
}
1142
if (result == WD_SUCCESS) {
1143
return result;
1144
}
1145
}
1146
}
1147
}
1148
}
1149
1150
rect->get_top(&top);
1151
rect->get_left(&left);
1152
rect->get_bottom(&bottom);
1153
rect->get_right(&right);
1154
}
1155
1156
long w = right - left;
1157
long h = bottom - top;
1158
1159
bool element_is_in_frame = this->AppendFrameDetails(frame_locations);
1160
if (!has_absolute_position_ready_to_return) {
1161
// On versions of IE prior to 8 on Vista, if the element is out of the
1162
// viewport this would seem to return 0,0,0,0. IE 8 returns position in
1163
// the DOM regardless of whether it's in the browser viewport.
1164
long scroll_left, scroll_top = 0;
1165
element2->get_scrollLeft(&scroll_left);
1166
element2->get_scrollTop(&scroll_top);
1167
left += scroll_left;
1168
top += scroll_top;
1169
1170
// Only add the frame offset if the element is actually in a frame.
1171
if (element_is_in_frame) {
1172
LocationInfo frame_location = frame_locations->back();
1173
left += frame_location.x;
1174
top += frame_location.y;
1175
} else {
1176
LOG(DEBUG) << "Element is not in a frame";
1177
}
1178
}
1179
1180
location->x = left;
1181
location->y = top;
1182
location->width = w;
1183
location->height = h;
1184
1185
return WD_SUCCESS;
1186
}
1187
1188
bool Element::IsInline() {
1189
LOG(TRACE) << "Entering Element::IsInline";
1190
1191
// TODO(jimevans): Clean up this extreme lameness.
1192
// We should be checking styles here for whether the
1193
// element is inline or not.
1194
CComPtr<IHTMLAnchorElement> anchor;
1195
HRESULT hr = this->element_->QueryInterface(&anchor);
1196
if (anchor) {
1197
return true;
1198
}
1199
1200
CComPtr<IHTMLSpanElement> span;
1201
hr = this->element_->QueryInterface(&span);
1202
if (span) {
1203
return true;
1204
}
1205
1206
CComPtr<IHTMLLabelElement> label;
1207
hr = this->element_->QueryInterface(&label);
1208
if (label) {
1209
return true;
1210
}
1211
1212
return false;
1213
}
1214
1215
bool Element::RectHasNonZeroDimensions(IHTMLRect* rect) {
1216
LOG(TRACE) << "Entering Element::RectHasNonZeroDimensions";
1217
1218
long top = 0, bottom = 0, left = 0, right = 0;
1219
1220
rect->get_top(&top);
1221
rect->get_left(&left);
1222
rect->get_bottom(&bottom);
1223
rect->get_right(&right);
1224
1225
long w = right - left;
1226
long h = bottom - top;
1227
1228
return w > 0 && h > 0;
1229
}
1230
1231
bool Element::AppendFrameDetails(std::vector<LocationInfo>* frame_locations) {
1232
LOG(TRACE) << "Entering Element::GetFrameDetails";
1233
1234
if (frame_locations == nullptr) {
1235
return false;
1236
}
1237
1238
CComPtr<IHTMLDocument2> owner_doc;
1239
int status_code = this->GetContainingDocument(true, &owner_doc);
1240
if (status_code != WD_SUCCESS) {
1241
LOG(WARN) << "Unable to get containing document";
1242
return false;
1243
}
1244
1245
CComPtr<IHTMLWindow2> owner_doc_window;
1246
HRESULT hr = owner_doc->get_parentWindow(&owner_doc_window);
1247
if (!owner_doc_window) {
1248
LOG(WARN) << "Unable to get parent window, call to IHTMLDocument2::get_parentWindow failed";
1249
return false;
1250
}
1251
1252
// Get the parent window to the current window, where "current window" is
1253
// the window containing the parent document of this element. If that parent
1254
// window exists, and it is not the same as the current window, we assume
1255
// this element exists inside a frame or iframe. If it is in a frame, get
1256
// the parent document containing the frame, so we can get the information
1257
// about the frame or iframe element hosting the document of this element.
1258
CComPtr<IHTMLWindow2> parent_window;
1259
hr = owner_doc_window->get_parent(&parent_window);
1260
if (parent_window && !owner_doc_window.IsEqualObject(parent_window)) {
1261
LOG(DEBUG) << "Element is in a frame.";
1262
CComPtr<IHTMLDocument2> parent_doc;
1263
status_code = this->GetDocumentFromWindow(parent_window, &parent_doc);
1264
1265
CComPtr<IHTMLFramesCollection2> frames;
1266
hr = parent_doc->get_frames(&frames);
1267
1268
long frame_count(0);
1269
hr = frames->get_length(&frame_count);
1270
CComVariant index;
1271
index.vt = VT_I4;
1272
for (long i = 0; i < frame_count; ++i) {
1273
// See if the document in each frame is this element's
1274
// owner document.
1275
index.lVal = i;
1276
CComVariant result;
1277
hr = frames->item(&index, &result);
1278
CComPtr<IHTMLWindow2> frame_window;
1279
result.pdispVal->QueryInterface<IHTMLWindow2>(&frame_window);
1280
if (!frame_window) {
1281
// Frame is not an HTML frame.
1282
continue;
1283
}
1284
1285
CComPtr<IHTMLDocument2> frame_doc;
1286
status_code = this->GetDocumentFromWindow(frame_window, &frame_doc);
1287
1288
if (frame_doc.IsEqualObject(owner_doc)) {
1289
// The document in this frame *is* this element's owner
1290
// document. Get the frameElement property of the document's
1291
// containing window (which is itself an HTML element, either
1292
// a frame or an iframe). Then get the x and y coordinates of
1293
// that frame element.
1294
// N.B. We must use JavaScript here, as directly using
1295
// IHTMLWindow4.get_frameElement() returns E_NOINTERFACE under
1296
// some circumstances.
1297
LOG(DEBUG) << "Located host frame. Attempting to get hosting element";
1298
std::wstring script_source = L"(function(){ return function() { return arguments[0].frameElement };})();";
1299
Script script_wrapper(frame_doc, script_source, 1);
1300
CComVariant window_variant(frame_window);
1301
script_wrapper.AddArgument(window_variant);
1302
status_code = script_wrapper.Execute();
1303
CComPtr<IHTMLFrameBase> frame_base;
1304
if (status_code == WD_SUCCESS) {
1305
hr = script_wrapper.result().pdispVal->QueryInterface<IHTMLFrameBase>(&frame_base);
1306
if (FAILED(hr)) {
1307
LOG(WARN) << "Found the frame element, but could not QueryInterface to IHTMLFrameBase.";
1308
}
1309
} else {
1310
// Can't get the frameElement property, likely because the frames are from different
1311
// domains. So start at the parent document, and use getElementsByTagName to retrieve
1312
// all of the iframe elements (if there are no iframe elements, get the frame elements)
1313
// **** BIG HUGE ASSUMPTION!!! ****
1314
// The index of the frame from the document.frames collection will correspond to the
1315
// index into the collection of iframe/frame elements returned by getElementsByTagName.
1316
LOG(WARN) << "Attempting to get frameElement via JavaScript failed. "
1317
<< "This usually means the frame is in a different domain than the parent frame. "
1318
<< "Browser security against cross-site scripting attacks will not allow this. "
1319
<< "Attempting alternative method.";
1320
long collection_count = 0;
1321
CComPtr<IDispatch> element_dispatch;
1322
CComPtr<IHTMLDocument3> doc;
1323
parent_doc->QueryInterface<IHTMLDocument3>(&doc);
1324
if (doc) {
1325
LOG(DEBUG) << "Looking for <iframe> elements in parent document.";
1326
CComBSTR iframe_tag_name = L"iframe";
1327
CComPtr<IHTMLElementCollection> iframe_collection;
1328
hr = doc->getElementsByTagName(iframe_tag_name, &iframe_collection);
1329
hr = iframe_collection->get_length(&collection_count);
1330
if (collection_count != 0) {
1331
if (collection_count > index.lVal) {
1332
LOG(DEBUG) << "Found <iframe> elements in parent document, retrieving element" << index.lVal << ".";
1333
hr = iframe_collection->item(index, index, &element_dispatch);
1334
hr = element_dispatch->QueryInterface<IHTMLFrameBase>(&frame_base);
1335
}
1336
} else {
1337
LOG(DEBUG) << "No <iframe> elements, looking for <frame> elements in parent document.";
1338
CComBSTR frame_tag_name = L"frame";
1339
CComPtr<IHTMLElementCollection> frame_collection;
1340
hr = doc->getElementsByTagName(frame_tag_name, &frame_collection);
1341
hr = frame_collection->get_length(&collection_count);
1342
if (collection_count > index.lVal) {
1343
LOG(DEBUG) << "Found <frame> elements in parent document, retrieving element" << index.lVal << ".";
1344
hr = frame_collection->item(index, index, &element_dispatch);
1345
hr = element_dispatch->QueryInterface<IHTMLFrameBase>(&frame_base);
1346
}
1347
}
1348
} else {
1349
LOG(WARN) << "QueryInterface of parent document to IHTMLDocument3 failed.";
1350
}
1351
}
1352
1353
if (frame_base) {
1354
LOG(DEBUG) << "Successfully found frame hosting element";
1355
LocationInfo frame_doc_info;
1356
bool doc_dimensions_success = DocumentHost::GetDocumentDimensions(
1357
frame_doc,
1358
&frame_doc_info);
1359
1360
// Wrap the element so we can find its location. Note that
1361
// GetLocation() may recursively call into this method.
1362
CComPtr<IHTMLElement> frame_element;
1363
frame_base->QueryInterface<IHTMLElement>(&frame_element);
1364
Element element_wrapper(frame_element, this->containing_window_handle_);
1365
CComPtr<IHTMLStyle> style;
1366
frame_element->get_style(&style);
1367
1368
LocationInfo frame_location = {};
1369
status_code = element_wrapper.GetLocation(&frame_location,
1370
frame_locations);
1371
1372
if (status_code == WD_SUCCESS) {
1373
// Take the border of the frame element into account.
1374
// N.B. We don't have to do this for non-frame elements,
1375
// because the border is part of the hit-test region. For
1376
// finding offsets to get absolute position of elements
1377
// within frames, the origin of the frame document is offset
1378
// by the border width.
1379
CComPtr<IHTMLElement2> border_width_element;
1380
frame_element->QueryInterface<IHTMLElement2>(&border_width_element);
1381
1382
long left_border_width = 0;
1383
border_width_element->get_clientLeft(&left_border_width);
1384
frame_location.x += left_border_width;
1385
1386
long top_border_width = 0;
1387
border_width_element->get_clientTop(&top_border_width);
1388
frame_location.y += top_border_width;
1389
1390
// Take into account the presence of scrollbars in the frame.
1391
if (doc_dimensions_success) {
1392
if (frame_doc_info.height > frame_location.height) {
1393
int horizontal_scrollbar_height = ::GetSystemMetrics(SM_CYHSCROLL);
1394
frame_location.height -= horizontal_scrollbar_height;
1395
}
1396
if (frame_doc_info.width > frame_location.width) {
1397
int vertical_scrollbar_width = ::GetSystemMetrics(SM_CXVSCROLL);
1398
frame_location.width -= vertical_scrollbar_width;
1399
}
1400
}
1401
frame_locations->push_back(frame_location);
1402
}
1403
return true;
1404
}
1405
}
1406
}
1407
}
1408
1409
// If we reach here, the element isn't in a frame/iframe.
1410
return false;
1411
}
1412
1413
bool Element::GetClickableViewPortLocation(const bool document_contains_frames, LocationInfo* location) {
1414
LOG(TRACE) << "Entering Element::GetClickableViewPortLocation";
1415
1416
WINDOWINFO window_info;
1417
window_info.cbSize = sizeof(WINDOWINFO);
1418
BOOL get_window_info_result = ::GetWindowInfo(this->containing_window_handle_, &window_info);
1419
if (get_window_info_result == FALSE) {
1420
LOGERR(WARN) << "Cannot determine size of window, call to GetWindowInfo API failed";
1421
return false;
1422
}
1423
1424
long window_width = window_info.rcClient.right - window_info.rcClient.left;
1425
long window_height = window_info.rcClient.bottom - window_info.rcClient.top;
1426
1427
// If we're not on the top-level document, we can assume that the view port
1428
// includes the entire client window, since scrollIntoView should do the
1429
// right thing and make it visible. Otherwise, we prefer getting the view
1430
// port size by either getting the window.innerWidth and .innerHeight, or
1431
// by using documentElement.clientHeight and .clientWidth.
1432
if (!document_contains_frames) {
1433
CComPtr<IHTMLDocument2> doc;
1434
int status_code = this->GetContainingDocument(false, &doc);
1435
if (status_code == WD_SUCCESS) {
1436
bool used_window_properties = false;
1437
CComPtr<IHTMLWindow2> parent_window;
1438
HRESULT hr = doc->get_parentWindow(&parent_window);
1439
if (SUCCEEDED(hr) && parent_window) {
1440
CComPtr<IHTMLWindow7> window;
1441
hr = parent_window->QueryInterface<IHTMLWindow7>(&window);
1442
if (SUCCEEDED(hr) && window) {
1443
window->get_innerHeight(&window_height);
1444
window->get_innerWidth(&window_width);
1445
used_window_properties = true;
1446
}
1447
}
1448
1449
// If using the window object's innerWidth and innerHeight properties
1450
// failed, then fall back to the document element's clientWidth and
1451
// clientHeight properties.
1452
if (!used_window_properties) {
1453
int document_mode = DocumentHost::GetDocumentMode(doc);
1454
CComPtr<IHTMLDocument3> document_element_doc;
1455
CComPtr<IHTMLElement> document_element;
1456
hr = doc->QueryInterface<IHTMLDocument3>(&document_element_doc);
1457
if (SUCCEEDED(hr) && document_element_doc) {
1458
hr = document_element_doc->get_documentElement(&document_element);
1459
}
1460
if (SUCCEEDED(hr) && document_mode > 5 && document_element) {
1461
CComPtr<IHTMLElement2> size_element;
1462
hr = document_element->QueryInterface<IHTMLElement2>(&size_element);
1463
size_element->get_clientHeight(&window_height);
1464
size_element->get_clientWidth(&window_width);
1465
} else {
1466
// This branch is only included if getting documentElement fails.
1467
LOG(WARN) << "Document containing element does not contains frames, "
1468
<< "but getting the documentElement property failed, or the "
1469
<< "doctype has thrown the browser into pre-IE6 rendering. "
1470
<< "The view port calculation may be inaccurate";
1471
LocationInfo document_info;
1472
DocumentHost::GetDocumentDimensions(doc, &document_info);
1473
if (document_info.height > window_height) {
1474
int vertical_scrollbar_width = ::GetSystemMetrics(SM_CXVSCROLL);
1475
window_width -= vertical_scrollbar_width;
1476
}
1477
if (document_info.width > window_width) {
1478
int horizontal_scrollbar_height = ::GetSystemMetrics(SM_CYHSCROLL);
1479
window_height -= horizontal_scrollbar_height;
1480
}
1481
}
1482
}
1483
}
1484
}
1485
1486
// Hurrah! Now we know what the visible area of the viewport is
1487
// N.B. There is an n-pixel sized area next to the client area border
1488
// where clicks are interpreted as a click on the window border, not
1489
// within the client area. Some clicks may fail if they are close enough
1490
// to the border.
1491
location->width = window_width;
1492
location->height = window_height;
1493
return true;
1494
}
1495
1496
LocationInfo Element::CalculateClickPoint(const LocationInfo location, const bool document_contains_frames) {
1497
LOG(TRACE) << "Entering Element::CalculateClickPoint";
1498
1499
long corrected_width = location.width;
1500
long corrected_height = location.height;
1501
long corrected_x = location.x;
1502
long corrected_y = location.y;
1503
1504
LocationInfo clickable_viewport = {};
1505
bool result = this->GetClickableViewPortLocation(document_contains_frames,
1506
&clickable_viewport);
1507
1508
if (result) {
1509
// TODO: Handle the case where the center of the target element
1510
// is already in the view port. The code would look something like
1511
// the following:
1512
// If the center of the target element is already in the view port,
1513
// we don't need to adjust to find the "in view center point."
1514
// Technically, this is a deliberate violation of the spec.
1515
//long element_center_x = location.x + static_cast<long>(floor(location.width / 2.0));
1516
//long element_center_y = location.y + static_cast<long>(floor(location.height / 2.0));
1517
//if (element_center_x < 0 ||
1518
// element_center_x >= clickable_viewport.width ||
1519
// element_center_y < 0 ||
1520
// element_center_y >= clickable_viewport.height) {
1521
RECT element_rect;
1522
element_rect.left = location.x;
1523
element_rect.top = location.y;
1524
element_rect.right = location.x + location.width;
1525
element_rect.bottom = location.y + location.height;
1526
1527
RECT viewport_rect;
1528
viewport_rect.left = clickable_viewport.x;
1529
viewport_rect.top = clickable_viewport.y;
1530
viewport_rect.right = clickable_viewport.x + clickable_viewport.width;
1531
viewport_rect.bottom = clickable_viewport.y + clickable_viewport.height;
1532
1533
RECT intersect_rect;
1534
BOOL is_intersecting = ::IntersectRect(&intersect_rect,
1535
&element_rect,
1536
&viewport_rect);
1537
if (is_intersecting) {
1538
corrected_width = intersect_rect.right - intersect_rect.left;
1539
corrected_height = intersect_rect.bottom - intersect_rect.top;
1540
// If the x or y coordinate is greater than or equal to zero, the
1541
// initial location will already be correct, and not need to be
1542
// adjusted.
1543
if (location.x < 0) {
1544
corrected_x = 0;
1545
}
1546
if (location.y < 0) {
1547
corrected_y = 0;
1548
}
1549
}
1550
}
1551
1552
LocationInfo click_location = {};
1553
click_location.x = corrected_x + static_cast<long>(floor(corrected_width / 2.0));
1554
click_location.y = corrected_y + static_cast<long>(floor(corrected_height / 2.0));
1555
return click_location;
1556
}
1557
1558
bool Element::IsLocationInViewPort(const LocationInfo location, const bool document_contains_frames) {
1559
LOG(TRACE) << "Entering Element::IsLocationInViewPort";
1560
1561
LocationInfo clickable_viewport = {};
1562
bool result = this->GetClickableViewPortLocation(document_contains_frames, &clickable_viewport);
1563
if (!result) {
1564
// problem is already logged, so just return
1565
return false;
1566
}
1567
1568
if (location.x < 0 || location.x >= clickable_viewport.width) {
1569
LOG(WARN) << "X coordinate is out of element area";
1570
return false;
1571
}
1572
1573
// And in the Y?
1574
if (location.y < 0 || location.y >= clickable_viewport.height) {
1575
LOG(WARN) << "Y coordinate is out of element area";
1576
return false;
1577
}
1578
1579
return true;
1580
}
1581
1582
int Element::GetContainingDocument(const bool use_dom_node,
1583
IHTMLDocument2** doc) {
1584
LOG(TRACE) << "Entering Element::GetContainingDocument";
1585
1586
HRESULT hr = S_OK;
1587
CComPtr<IDispatch> dispatch_doc;
1588
1589
if (use_dom_node) {
1590
CComPtr<IHTMLDOMNode2> node;
1591
hr = this->element_->QueryInterface(&node);
1592
if (FAILED(hr)) {
1593
LOGHR(WARN, hr) << "Unable to cast element to IHTMLDomNode2";
1594
return ENOSUCHDOCUMENT;
1595
}
1596
1597
hr = node->get_ownerDocument(&dispatch_doc);
1598
if (FAILED(hr)) {
1599
LOGHR(WARN, hr) << "Unable to locate owning document, call to IHTMLDOMNode2::get_ownerDocument failed";
1600
return ENOSUCHDOCUMENT;
1601
}
1602
} else {
1603
hr = this->element_->get_document(&dispatch_doc);
1604
if (FAILED(hr)) {
1605
LOGHR(WARN, hr) << "Unable to locate document property, call to IHTMLELement::get_document failed";
1606
return ENOSUCHDOCUMENT;
1607
}
1608
}
1609
1610
try {
1611
hr = dispatch_doc.QueryInterface<IHTMLDocument2>(doc);
1612
if (FAILED(hr)) {
1613
LOGHR(WARN, hr) << "Found document but it's not the expected type (IHTMLDocument2)";
1614
return ENOSUCHDOCUMENT;
1615
}
1616
} catch(...) {
1617
LOG(WARN) << "Found document but it's not the expected type (IHTMLDocument2)";
1618
return ENOSUCHDOCUMENT;
1619
}
1620
1621
return WD_SUCCESS;
1622
}
1623
1624
int Element::GetDocumentFromWindow(IHTMLWindow2* parent_window,
1625
IHTMLDocument2** parent_doc) {
1626
LOG(TRACE) << "Entering Element::GetParentDocument";
1627
1628
HRESULT hr = parent_window->get_document(parent_doc);
1629
if (FAILED(hr)) {
1630
if (hr == E_ACCESSDENIED) {
1631
// Cross-domain documents may throw Access Denied. If so,
1632
// get the document through the IWebBrowser2 interface.
1633
CComPtr<IServiceProvider> service_provider;
1634
hr = parent_window->QueryInterface<IServiceProvider>(&service_provider);
1635
if (FAILED(hr)) {
1636
LOGHR(WARN, hr) << "Unable to get browser, call to IHTMLWindow2::QueryInterface failed for IServiceProvider";
1637
return ENOSUCHDOCUMENT;
1638
}
1639
CComPtr<IWebBrowser2> window_browser;
1640
hr = service_provider->QueryService(IID_IWebBrowserApp, &window_browser);
1641
if (FAILED(hr)) {
1642
LOGHR(WARN, hr) << "Unable to get browser, call to IServiceProvider::QueryService failed for IID_IWebBrowserApp";
1643
return ENOSUCHDOCUMENT;
1644
}
1645
CComPtr<IDispatch> parent_doc_dispatch;
1646
hr = window_browser->get_Document(&parent_doc_dispatch);
1647
if (FAILED(hr)) {
1648
LOGHR(WARN, hr) << "Unable to get document, call to IWebBrowser2::get_Document failed";
1649
return ENOSUCHDOCUMENT;
1650
}
1651
try {
1652
hr = parent_doc_dispatch->QueryInterface<IHTMLDocument2>(parent_doc);
1653
if (FAILED(hr)) {
1654
LOGHR(WARN, hr) << "Unable to get document, QueryInterface for IHTMLDocument2 failed";
1655
return ENOSUCHDOCUMENT;
1656
}
1657
} catch(...) {
1658
LOG(WARN) << "Unable to get document, exception thrown attempting to QueryInterface for IHTMLDocument2";
1659
return ENOSUCHDOCUMENT;
1660
}
1661
} else {
1662
LOGHR(WARN, hr) << "Unable to get document, IHTMLWindow2::get_document failed with error code other than E_ACCESSDENIED";
1663
return ENOSUCHDOCUMENT;
1664
}
1665
}
1666
return WD_SUCCESS;
1667
}
1668
1669
bool Element::IsAttachedToDom() {
1670
// Verify that the element is still valid by getting the document
1671
// element and calling IHTMLElement::contains() to see if the document
1672
// contains this element.
1673
if (this->element_) {
1674
CComPtr<IHTMLDOMNode2> node;
1675
HRESULT hr = this->element_->QueryInterface<IHTMLDOMNode2>(&node);
1676
if (FAILED(hr)) {
1677
LOGHR(WARN, hr) << "Unable to cast element to IHTMLDomNode2";
1678
return false;
1679
}
1680
1681
CComPtr<IDispatch> dispatch_doc;
1682
hr = node->get_ownerDocument(&dispatch_doc);
1683
if (FAILED(hr)) {
1684
LOGHR(WARN, hr) << "Unable to locate owning document, call to IHTMLDOMNode2::get_ownerDocument failed";
1685
return false;
1686
}
1687
1688
if (dispatch_doc) {
1689
CComPtr<IHTMLDocument3> doc;
1690
hr = dispatch_doc.QueryInterface<IHTMLDocument3>(&doc);
1691
if (FAILED(hr)) {
1692
LOGHR(WARN, hr) << "Found document but it's not the expected type (IHTMLDocument3)";
1693
return false;
1694
}
1695
1696
CComPtr<IHTMLElement> document_element;
1697
hr = doc->get_documentElement(&document_element);
1698
if (FAILED(hr)) {
1699
LOGHR(WARN, hr) << "Unable to locate document element, call to IHTMLDocument3::get_documentElement failed";
1700
return false;
1701
}
1702
1703
if (document_element) {
1704
VARIANT_BOOL contains(VARIANT_FALSE);
1705
hr = document_element->contains(this->element_, &contains);
1706
if (FAILED(hr)) {
1707
LOGHR(WARN, hr) << "Call to IHTMLElement::contains failed";
1708
return false;
1709
}
1710
1711
return contains == VARIANT_TRUE;
1712
}
1713
}
1714
}
1715
return false;
1716
}
1717
1718
bool Element::IsDocumentFocused(IHTMLDocument2* focused_doc) {
1719
CComPtr<IDispatch> parent_doc_dispatch;
1720
this->element_->get_document(&parent_doc_dispatch);
1721
1722
if (parent_doc_dispatch.IsEqualObject(focused_doc)) {
1723
return true;
1724
} else {
1725
LOG(WARN) << "Found managed element's document is not currently focused";
1726
}
1727
return false;
1728
}
1729
1730
bool Element::HasFirstChildTextNodeOfMultipleChildren() {
1731
CComPtr<IHTMLDOMNode> element_node;
1732
HRESULT hr = this->element_.QueryInterface<IHTMLDOMNode>(&element_node);
1733
if (FAILED(hr)) {
1734
LOGHR(WARN, hr) << "QueryInterface for IHTMLDOMNode on element failed.";
1735
return false;
1736
}
1737
1738
CComPtr<IDispatch> child_nodes_dispatch;
1739
hr = element_node->get_childNodes(&child_nodes_dispatch);
1740
if (FAILED(hr)) {
1741
LOGHR(WARN, hr) << "Call to get_childNodes on element failed.";
1742
return false;
1743
}
1744
1745
CComPtr<IHTMLDOMChildrenCollection> child_nodes;
1746
hr = child_nodes_dispatch.QueryInterface<IHTMLDOMChildrenCollection>(&child_nodes);
1747
1748
long length = 0;
1749
hr = child_nodes->get_length(&length);
1750
if (FAILED(hr)) {
1751
LOGHR(WARN, hr) << "Call to get_length on child nodes collection failed.";
1752
return false;
1753
}
1754
1755
// If the element has no children, then it has no single text node child.
1756
// If the element has only one child, then the element itself should be seen
1757
// as the correct size by the caller. Only in the case where we have multiple
1758
// children, and the first is a text element containing non-whitespace text
1759
// should we have to worry about using the text node as the focal point.
1760
if (length > 1) {
1761
CComPtr<IDispatch> child_dispatch;
1762
hr = child_nodes->item(0, &child_dispatch);
1763
if (FAILED(hr)) {
1764
LOGHR(WARN, hr) << "Call to item(0) on child nodes collection failed.";
1765
return false;
1766
}
1767
1768
CComPtr<IHTMLDOMNode> child_node;
1769
hr = child_dispatch.QueryInterface<IHTMLDOMNode>(&child_node);
1770
if (FAILED(hr)) {
1771
LOGHR(WARN, hr) << "QueryInterface for IHTMLDOMNode on child node failed.";
1772
return false;
1773
}
1774
1775
long node_type = 0;
1776
hr = child_node->get_nodeType(&node_type);
1777
if (FAILED(hr)) {
1778
LOGHR(WARN, hr) << "Call to get_nodeType on child node failed.";
1779
return false;
1780
}
1781
1782
if (node_type == 3) {
1783
CComVariant node_value;
1784
hr = child_node->get_nodeValue(&node_value);
1785
if (FAILED(hr)) {
1786
LOGHR(WARN, hr) << "Call to get_nodeValue on child node failed.";
1787
return false;
1788
}
1789
1790
if (node_value.vt != VT_BSTR) {
1791
// nodeValue is not a string.
1792
return false;
1793
}
1794
1795
CComBSTR bstr = node_value.bstrVal;
1796
std::wstring node_text = node_value.bstrVal;
1797
if (StringUtilities::Trim(node_text) != L"") {
1798
// This element has a text node only if the text node
1799
// contains actual text other than whitespace.
1800
return true;
1801
}
1802
}
1803
}
1804
return false;
1805
}
1806
1807
bool Element::GetTextBoundaries(LocationInfo* text_info) {
1808
CComPtr<IHTMLDocument2> doc;
1809
this->GetContainingDocument(false, &doc);
1810
CComPtr<IHTMLElement> body_element;
1811
HRESULT hr = doc->get_body(&body_element);
1812
if (FAILED(hr)) {
1813
LOGHR(WARN, hr) << "Call to get_body on document failed.";
1814
return false;
1815
}
1816
1817
CComPtr<IHTMLBodyElement> body;
1818
hr = body_element.QueryInterface<IHTMLBodyElement>(&body);
1819
if (FAILED(hr)) {
1820
LOGHR(WARN, hr) << "QueryInterface for IHTMLBodyElement on body element failed.";
1821
return false;
1822
}
1823
1824
CComPtr<IHTMLTxtRange> range;
1825
hr = body->createTextRange(&range);
1826
if (FAILED(hr)) {
1827
LOGHR(WARN, hr) << "Call to createTextRange on body failed.";
1828
return false;
1829
}
1830
1831
hr = range->moveToElementText(this->element_);
1832
if (FAILED(hr)) {
1833
LOGHR(WARN, hr) << "Call to moveToElementText on range failed.";
1834
return false;
1835
}
1836
1837
CComPtr<IHTMLTextRangeMetrics> range_metrics;
1838
hr = range.QueryInterface<IHTMLTextRangeMetrics>(&range_metrics);
1839
if (FAILED(hr)) {
1840
LOGHR(WARN, hr) << "QueryInterface for IHTMLTextRangeMetrics on range failed.";
1841
return false;
1842
}
1843
1844
long height = 0;
1845
hr = range_metrics->get_boundingHeight(&height);
1846
if (FAILED(hr)) {
1847
LOGHR(WARN, hr) << "Call to get_boundingHeight on range metrics failed.";
1848
return false;
1849
}
1850
1851
long width = 0;
1852
hr = range_metrics->get_boundingWidth(&width);
1853
if (FAILED(hr)) {
1854
LOGHR(WARN, hr) << "Call to get_boundingWidth on range metrics failed.";
1855
return false;
1856
}
1857
1858
long top = 0;
1859
hr = range_metrics->get_offsetTop(&top);
1860
if (FAILED(hr)) {
1861
LOGHR(WARN, hr) << "Call to get_offsetTop on range metrics failed.";
1862
return false;
1863
}
1864
1865
long left = 0;
1866
hr = range_metrics->get_offsetLeft(&left);
1867
if (FAILED(hr)) {
1868
LOGHR(WARN, hr) << "Call to get_offsetLeft on range metrics failed.";
1869
return false;
1870
}
1871
1872
text_info->x = left;
1873
text_info->y = top;
1874
text_info->height = height;
1875
text_info->width = width;
1876
return true;
1877
}
1878
1879
} // namespace webdriver
1880
1881