// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Ignoring code analysis warnings for: // "'argument n' might be '0': this does not adhere to the specification for // the function 'IHTMLDocument4::createEventObject'", and "'argument n' might // be null: this does not adhere to the specification for the function // 'IHTMLDocument4::createEventObject'", and. // IHTMLDocument4::createEventObject() should have its first argument set to // NULL to create an empty event object, per documentation at: // http://msdn.microsoft.com/en-us/library/aa752524(v=vs.85).aspx #pragma warning (disable: 6309) #pragma warning (disable: 6387) #include "Element.h" #include #include "errorcodes.h" #include "logging.h" #include "json.h" #include "Browser.h" #include "Generated/atoms.h" #include "Script.h" #include "StringUtilities.h" #include "VariantUtilities.h" #include "WebDriverConstants.h" namespace webdriver { Element::Element(IHTMLElement* element, HWND containing_window_handle) { LOG(TRACE) << "Entering Element::Element"; // NOTE: COM should be initialized on this thread, so we // could use CoCreateGuid() and StringFromGUID2() instead. UUID guid; RPC_WSTR guid_string = NULL; RPC_STATUS status = ::UuidCreate(&guid); if (status != RPC_S_OK) { // If we encounter an error, not bloody much we can do about it. // Just log it and continue. LOG(WARN) << "UuidCreate returned a status other then RPC_S_OK: " << status; } status = ::UuidToString(&guid, &guid_string); if (status != RPC_S_OK) { // If we encounter an error, not bloody much we can do about it. // Just log it and continue. LOG(WARN) << "UuidToString returned a status other then RPC_S_OK: " << status; } // RPC_WSTR is currently typedef'd in RpcDce.h (pulled in by rpc.h) // as unsigned short*. It needs to be typedef'd as wchar_t* wchar_t* cast_guid_string = reinterpret_cast(guid_string); this->element_id_ = StringUtilities::ToString(cast_guid_string); ::RpcStringFree(&guid_string); this->element_ = element; this->containing_window_handle_ = containing_window_handle; } Element::Element(IHTMLElement* element, HWND containing_window_handle, const std::string& element_id) { this->element_ = element; this->element_id_ = element_id; this->containing_window_handle_ = containing_window_handle; } Element::~Element(void) { } Json::Value Element::ConvertToJson() { LOG(TRACE) << "Entering Element::ConvertToJson"; Json::Value json_wrapper; json_wrapper[JSON_ELEMENT_PROPERTY_NAME] = this->element_id_; return json_wrapper; } int Element::IsDisplayed(bool ignore_opacity, bool* result) { LOG(TRACE) << "Entering Element::IsDisplayed"; int status_code = WD_SUCCESS; // The atom is just the definition of an anonymous // function: "function() {...}"; Wrap it in another function so we can // invoke it with our arguments without polluting the current namespace. std::wstring script_source(L"(function() { return ("); script_source += atoms::asString(atoms::IS_DISPLAYED); script_source += L")})();"; CComPtr doc; this->GetContainingDocument(false, &doc); // N.B., The second argument to the IsDisplayed atom is "ignoreOpacity". Script script_wrapper(doc, script_source, 2); script_wrapper.AddArgument(this->element_); script_wrapper.AddArgument(ignore_opacity); status_code = script_wrapper.Execute(); if (status_code == WD_SUCCESS) { *result = script_wrapper.result().boolVal == VARIANT_TRUE; } else { LOG(WARN) << "Failed to determine is element displayed"; } return status_code; } std::string Element::GetTagName() { LOG(TRACE) << "Entering Element::GetTagName"; CComBSTR tag_name_bstr; HRESULT hr = this->element_->get_tagName(&tag_name_bstr); if (FAILED(hr)) { LOGHR(WARN, hr) << "Failed calling IHTMLElement::get_tagName"; return ""; } std::wstring converted_tag_name = tag_name_bstr; std::string tag_name = StringUtilities::ToString(converted_tag_name); std::transform(tag_name.begin(), tag_name.end(), tag_name.begin(), ::tolower); return tag_name; } bool Element::IsEnabled() { LOG(TRACE) << "Entering Element::IsEnabled"; bool result = false; // The atom is just the definition of an anonymous // function: "function() {...}"; Wrap it in another function so we can // invoke it with our arguments without polluting the current namespace. std::wstring script_source(L"(function() { return ("); script_source += atoms::asString(atoms::IS_ENABLED); script_source += L")})();"; CComPtr doc; this->GetContainingDocument(false, &doc); if (this->IsXmlDocument(doc)) { return false; } Script script_wrapper(doc, script_source, 1); script_wrapper.AddArgument(this->element_); int status_code = script_wrapper.Execute(); if (status_code == WD_SUCCESS) { result = script_wrapper.result().boolVal == VARIANT_TRUE; } else { LOG(WARN) << "Failed to determine is element enabled"; } return result; } bool Element::IsXmlDocument(IHTMLDocument2* doc) { LOG(TRACE) << "Entering Element::IsXmlDocument"; // If the document has an xmlVersion property, it can be either an XML // document or an XHTML document. Otherwise, it's an HTML document. CComPtr xml_version_document; HRESULT hr = doc->QueryInterface(&xml_version_document); if (SUCCEEDED(hr) && xml_version_document) { CComBSTR xml_version = ""; hr = xml_version_document->get_xmlVersion(&xml_version); if (SUCCEEDED(hr) && xml_version && xml_version != L"") { // The document is either XML or XHTML, so to differentiate between // the two cases, check for a doctype of "html". If we can't find // a doctype property, or the doctype is anything other than "html", // the document is an XML document. CComPtr doc_type_document; hr = doc->QueryInterface(&doc_type_document); if (SUCCEEDED(hr) && doc_type_document) { CComPtr doc_type_dom_node; hr = doc_type_document->get_doctype(&doc_type_dom_node); if (SUCCEEDED(hr) && doc_type_dom_node) { CComPtr doc_type; hr = doc_type_dom_node->QueryInterface(&doc_type); if (SUCCEEDED(hr) && doc_type) { CComBSTR type_name_bstr = L""; hr = doc_type->get_name(&type_name_bstr); type_name_bstr.ToLower(); std::wstring type_name(type_name_bstr); LOG(INFO) << LOGWSTRING(type_name); if (SUCCEEDED(hr) && type_name != L"html") { return true; } } } else { return true; } } } } return false; } bool Element::IsInteractable() { LOG(TRACE) << "Entering Element::IsInteractable"; bool result = false; // The atom is just the definition of an anonymous // function: "function() {...}"; Wrap it in another function so we can // invoke it with our arguments without polluting the current namespace. std::wstring script_source(L"(function() { return ("); script_source += atoms::asString(atoms::IS_INTERACTABLE); script_source += L")})();"; CComPtr doc; this->GetContainingDocument(false, &doc); Script script_wrapper(doc, script_source, 1); script_wrapper.AddArgument(this->element_); int status_code = script_wrapper.Execute(); if (status_code == WD_SUCCESS) { result = script_wrapper.result().boolVal == VARIANT_TRUE; } else { LOG(WARN) << "Failed to determine is element enabled"; } return result; } bool Element::IsFocusable() { LOG(TRACE) << "Entering Element::IsFocusable"; CComPtr body; HRESULT hr = this->element_->QueryInterface(&body); if (SUCCEEDED(hr) && body) { // The element is explicitly focusable. return true; } CComPtr doc; this->GetContainingDocument(false, &doc); CComPtr document_element_doc; hr = doc->QueryInterface(&document_element_doc); if (SUCCEEDED(hr) && document_element_doc) { CComPtr doc_element; hr = document_element_doc->get_documentElement(&doc_element); if (SUCCEEDED(hr) && doc_element && this->element_.IsEqualObject(doc_element)) { // The document's documentElement is explicitly focusable. return true; } } return false; } bool Element::IsObscured(LocationInfo* click_location, long* obscuring_element_index, std::string* obscuring_element_description) { CComPtr svg_element; HRESULT hr = this->element_->QueryInterface(&svg_element); if (SUCCEEDED(hr) && svg_element != NULL) { // SVG elements can have complex paths making them non-hierarchical // when drawn. We'll just assume the user knows what they're doing // and bail on this test here. return false; } // If an element has a style value where pointer-events is set to 'none', // the element is "obscured" by definition, since any mouse interaction // will not be handled by the element. CComPtr computed_style; if (this->GetComputedStyle(&computed_style)) { CComBSTR pointer_events_value = L""; hr = computed_style->get_pointerEvents(&pointer_events_value); if (SUCCEEDED(hr) && pointer_events_value == L"none") { return true; } } // The element being obscured only makes sense within the context // of its own document, even if it's not in the top-level document. LocationInfo element_location = {}; int status_code = this->GetLocation(&element_location, nullptr); *click_location = this->CalculateClickPoint(element_location, false); long x = click_location->x; long y = click_location->y; bool is_inline = this->IsInline(); CComPtr doc; this->GetContainingDocument(false, &doc); CComPtr element_hit; hr = doc->elementFromPoint(x, y, &element_hit); if (SUCCEEDED(hr) && element_hit) { if (element_.IsEqualObject(element_hit)) { // Short circuit the use of elementsFromPoint if we don't // have to use it. return false; } else { // Short circuit in the case where this element is specifically // an "inline" element (