| // Copyright 2011 Software Freedom Conservancy | |
| // Licensed 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. | |
| #include "Generated/atoms.h" | |
| #include "Generated/sizzle.h" | |
| #include "IECommandExecutor.h" | |
| #include "logging.h" | |
| namespace webdriver { | |
| ElementFinder::ElementFinder() { | |
| } | |
| ElementFinder::~ElementFinder() { | |
| } | |
| int ElementFinder::FindElement(const IECommandExecutor& executor, | |
| const ElementHandle parent_wrapper, | |
| const std::wstring& mechanism, | |
| const std::wstring& criteria, | |
| Json::Value* found_element) { | |
| LOG(TRACE) << "Entering ElementFinder::FindElement"; | |
| BrowserHandle browser; | |
| int status_code = executor.GetCurrentBrowser(&browser); | |
| if (status_code == SUCCESS) { | |
| if (mechanism == L"css") { | |
| return this->FindElementByCssSelector(executor, | |
| parent_wrapper, | |
| criteria, | |
| found_element); | |
| } else { | |
| std::wstring sanitized_criteria = criteria; | |
| this->SanitizeCriteria(mechanism, &sanitized_criteria); | |
| std::wstring criteria_object_script = L"(function() { return function(){ return { \"" + | |
| mechanism + | |
| L"\" : \"" + | |
| sanitized_criteria + L"\" }; };})();"; | |
| CComPtr<IHTMLDocument2> doc; | |
| browser->GetDocument(&doc); | |
| Script criteria_wrapper(doc, criteria_object_script, 0); | |
| status_code = criteria_wrapper.Execute(); | |
| if (status_code == SUCCESS) { | |
| CComVariant criteria_object; | |
| HRESULT hr = ::VariantCopy(&criteria_object, | |
| &criteria_wrapper.result()); | |
| // 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::FIND_ELEMENT); | |
| script_source += L")})();"; | |
| Script script_wrapper(doc, script_source, 2); | |
| script_wrapper.AddArgument(criteria_object); | |
| if (parent_wrapper) { | |
| script_wrapper.AddArgument(parent_wrapper->element()); | |
| } | |
| status_code = script_wrapper.Execute(); | |
| if (status_code == SUCCESS) { | |
| if (script_wrapper.ResultIsElement()) { | |
| script_wrapper.ConvertResultToJsonValue(executor, found_element); | |
| } else { | |
| LOG(WARN) << "Unable to find element by mechanism " | |
| << LOGWSTRING(mechanism.c_str()) << " and criteria " | |
| << LOGWSTRING(sanitized_criteria.c_str()); | |
| status_code = ENOSUCHELEMENT; | |
| } | |
| } else { | |
| // An error in the execution of the FindElement atom for XPath is assumed | |
| // to be a syntactically invalid XPath. | |
| if (mechanism == L"xpath") { | |
| LOG(WARN) << "Attempted to find element using invalid xpath: " | |
| << LOGWSTRING(sanitized_criteria.c_str()); | |
| status_code = EINVALIDSELECTOR; | |
| } else { | |
| LOG(WARN) << "Unexpected error attempting to find element by mechanism " | |
| << LOGWSTRING(mechanism.c_str()) << " with criteria " | |
| << LOGWSTRING(sanitized_criteria.c_str()); | |
| status_code = ENOSUCHELEMENT; | |
| } | |
| } | |
| } else { | |
| LOG(WARN) << "Unable to create criteria object for mechanism " | |
| << LOGWSTRING(mechanism.c_str()) << " and criteria " | |
| << LOGWSTRING(sanitized_criteria.c_str()); | |
| status_code = ENOSUCHELEMENT; | |
| } | |
| } | |
| } else { | |
| LOG(WARN) << "Unable to get browser"; | |
| } | |
| return status_code; | |
| } | |
| int ElementFinder::FindElements(const IECommandExecutor& executor, | |
| const ElementHandle parent_wrapper, | |
| const std::wstring& mechanism, | |
| const std::wstring& criteria, | |
| Json::Value* found_elements) { | |
| LOG(TRACE) << "Entering ElementFinder::FindElements"; | |
| BrowserHandle browser; | |
| int status_code = executor.GetCurrentBrowser(&browser); | |
| if (status_code == SUCCESS) { | |
| if (mechanism == L"css") { | |
| return this->FindElementsByCssSelector(executor, | |
| parent_wrapper, | |
| criteria, | |
| found_elements); | |
| } else { | |
| std::wstring sanitized_criteria = criteria; | |
| this->SanitizeCriteria(mechanism, &sanitized_criteria); | |
| std::wstring criteria_object_script = L"(function() { return function(){ return { \"" + mechanism + L"\" : \"" + sanitized_criteria + L"\" }; };})();"; | |
| CComPtr<IHTMLDocument2> doc; | |
| browser->GetDocument(&doc); | |
| Script criteria_wrapper(doc, criteria_object_script, 0); | |
| status_code = criteria_wrapper.Execute(); | |
| if (status_code == SUCCESS) { | |
| CComVariant criteria_object; | |
| HRESULT hr = ::VariantCopy(&criteria_object, | |
| &criteria_wrapper.result()); | |
| // 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::FIND_ELEMENTS); | |
| script_source += L")})();"; | |
| Script script_wrapper(doc, script_source, 2); | |
| script_wrapper.AddArgument(criteria_object); | |
| if (parent_wrapper) { | |
| script_wrapper.AddArgument(parent_wrapper->element()); | |
| } | |
| status_code = script_wrapper.Execute(); | |
| if (status_code == SUCCESS) { | |
| if (script_wrapper.ResultIsArray() || | |
| script_wrapper.ResultIsElementCollection()) { | |
| script_wrapper.ConvertResultToJsonValue(executor, found_elements); | |
| } else { | |
| LOG(WARN) << "Returned value is not an array or element collection"; | |
| status_code = ENOSUCHELEMENT; | |
| } | |
| } else { | |
| // An error in the execution of the FindElement atom for XPath is assumed | |
| // to be a syntactically invalid XPath. | |
| if (mechanism == L"xpath") { | |
| LOG(WARN) << "Attempted to find elements using invalid xpath: " | |
| << LOGWSTRING(sanitized_criteria.c_str()); | |
| status_code = EINVALIDSELECTOR; | |
| } else { | |
| LOG(WARN) << "Unexpected error attempting to find element by mechanism " | |
| << LOGWSTRING(mechanism.c_str()) << " and criteria " | |
| << LOGWSTRING(sanitized_criteria.c_str()); | |
| status_code = ENOSUCHELEMENT; | |
| } | |
| } | |
| } else { | |
| LOG(WARN) << "Unable to create criteria object for mechanism " | |
| << LOGWSTRING(mechanism.c_str()) << " and criteria " | |
| << LOGWSTRING(sanitized_criteria.c_str()); | |
| status_code = ENOSUCHELEMENT; | |
| } | |
| } | |
| } else { | |
| LOG(WARN) << "Unable to get browser"; | |
| } | |
| return status_code; | |
| } | |
| int ElementFinder::FindElementByCssSelector(const IECommandExecutor& executor, | |
| const ElementHandle parent_wrapper, | |
| const std::wstring& criteria, | |
| Json::Value* found_element) { | |
| LOG(TRACE) << "Entering ElementFinder::FindElementByCssSelector"; | |
| int result; | |
| BrowserHandle browser; | |
| result = executor.GetCurrentBrowser(&browser); | |
| if (result != SUCCESS) { | |
| LOG(WARN) << "Unable to get browser"; | |
| return result; | |
| } | |
| std::wstring script_source(L"(function() { return function(){ if (!window.Sizzle) {"); | |
| script_source += atoms::asString(atoms::SIZZLE); | |
| script_source += L"}\n"; | |
| script_source += L"var root = arguments[1] ? arguments[1] : document.documentElement;"; | |
| script_source += L"if (root['querySelector']) { return root.querySelector(arguments[0]); } "; | |
| script_source += L"var results = []; Sizzle(arguments[0], root, results);"; | |
| script_source += L"return results.length > 0 ? results[0] : null;"; | |
| script_source += L"};})();"; | |
| CComPtr<IHTMLDocument2> doc; | |
| browser->GetDocument(&doc); | |
| Script script_wrapper(doc, script_source, 2); | |
| script_wrapper.AddArgument(criteria); | |
| if (parent_wrapper) { | |
| CComPtr<IHTMLElement> parent(parent_wrapper->element()); | |
| IHTMLElement* parent_element_copy; | |
| HRESULT hr = parent.CopyTo(&parent_element_copy); | |
| script_wrapper.AddArgument(parent_element_copy); | |
| } | |
| result = script_wrapper.Execute(); | |
| if (result == SUCCESS) { | |
| if (!script_wrapper.ResultIsElement()) { | |
| LOG(WARN) << "Found result is not element"; | |
| result = ENOSUCHELEMENT; | |
| } else { | |
| result = script_wrapper.ConvertResultToJsonValue(executor, | |
| found_element); | |
| } | |
| } else { | |
| LOG(WARN) << "Unable to find elements"; | |
| result = ENOSUCHELEMENT; | |
| } | |
| return result; | |
| } | |
| int ElementFinder::FindElementsByCssSelector(const IECommandExecutor& executor, | |
| const ElementHandle parent_wrapper, | |
| const std::wstring& criteria, | |
| Json::Value* found_elements) { | |
| LOG(TRACE) << "Entering ElementFinder::FindElementsByCssSelector"; | |
| int result; | |
| BrowserHandle browser; | |
| result = executor.GetCurrentBrowser(&browser); | |
| if (result != SUCCESS) { | |
| LOG(WARN) << "Unable to get browser"; | |
| return result; | |
| } | |
| std::wstring script_source(L"(function() { return function(){ if (!window.Sizzle) {"); | |
| script_source += atoms::asString(atoms::SIZZLE); | |
| script_source += L"}\n"; | |
| script_source += L"var root = arguments[1] ? arguments[1] : document.documentElement;"; | |
| script_source += L"if (root['querySelectorAll']) { return root.querySelectorAll(arguments[0]); } "; | |
| script_source += L"var results = []; Sizzle(arguments[0], root, results);"; | |
| script_source += L"return results;"; | |
| script_source += L"};})();"; | |
| CComPtr<IHTMLDocument2> doc; | |
| browser->GetDocument(&doc); | |
| Script script_wrapper(doc, script_source, 2); | |
| script_wrapper.AddArgument(criteria); | |
| if (parent_wrapper) { | |
| // Use a copy for the parent element? | |
| CComPtr<IHTMLElement> parent(parent_wrapper->element()); | |
| IHTMLElement* parent_element_copy; | |
| HRESULT hr = parent.CopyTo(&parent_element_copy); | |
| script_wrapper.AddArgument(parent_element_copy); | |
| } | |
| result = script_wrapper.Execute(); | |
| if (result == SUCCESS) { | |
| CComVariant snapshot = script_wrapper.result(); | |
| std::wstring get_element_count_script = L"(function(){return function() {return arguments[0].length;}})();"; | |
| Script get_element_count_script_wrapper(doc, get_element_count_script, 1); | |
| get_element_count_script_wrapper.AddArgument(snapshot); | |
| result = get_element_count_script_wrapper.Execute(); | |
| if (result == SUCCESS) { | |
| if (!get_element_count_script_wrapper.ResultIsInteger()) { | |
| LOG(WARN) << "Found elements count is not integer"; | |
| result = EUNEXPECTEDJSERROR; | |
| } else { | |
| long length = get_element_count_script_wrapper.result().lVal; | |
| std::wstring get_next_element_script = L"(function(){return function() {return arguments[0][arguments[1]];}})();"; | |
| for (long i = 0; i < length; ++i) { | |
| Script get_element_script_wrapper(doc, get_next_element_script, 2); | |
| get_element_script_wrapper.AddArgument(snapshot); | |
| get_element_script_wrapper.AddArgument(i); | |
| result = get_element_script_wrapper.Execute(); | |
| if (result == SUCCESS) { | |
| Json::Value json_element; | |
| get_element_script_wrapper.ConvertResultToJsonValue(executor, | |
| &json_element); | |
| found_elements->append(json_element); | |
| } else { | |
| LOG(WARN) << "Unable to get " << i << " found element"; | |
| } | |
| } | |
| } | |
| } else { | |
| LOG(WARN) << "Unable to get count of found elements"; | |
| result = EUNEXPECTEDJSERROR; | |
| } | |
| } else { | |
| LOG(WARN) << "Execution returned error"; | |
| } | |
| return result; | |
| } | |
| void ElementFinder::SanitizeCriteria(const std::wstring& mechanism, | |
| std::wstring* criteria) { | |
| LOG(TRACE) << "Entering ElementFinder::SanitizeCriteria"; | |
| // Any finder mechanism where the value can have embedded quotation | |
| // marks needs to have those quotes escaped for calling into JavaScript. | |
| if (mechanism == L"linkText" || | |
| mechanism == L"partialLinkText" || | |
| mechanism == L"xpath") { | |
| this->ReplaceAllSubstrings(L"\\", L"\\\\", criteria); | |
| this->ReplaceAllSubstrings(L"\"", L"\\\"", criteria); | |
| } | |
| } | |
| void ElementFinder::ReplaceAllSubstrings(const std::wstring& to_replace, | |
| const std::wstring& replace_with, | |
| std::wstring* str) { | |
| LOG(TRACE) << "Entering ElementFinder::ReplaceAllSubstrings"; | |
| size_t pos = str->find(to_replace); | |
| while (pos != std::wstring::npos) { | |
| str->replace(pos, to_replace.length(), replace_with); | |
| pos = str->find(to_replace, pos + replace_with.length()); | |
| } | |
| } | |
| } // namespace webdriver |