// 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. #include "AsyncScriptExecutor.h" #include #include "errorcodes.h" #include "logging.h" #include "Element.h" #include "ElementRepository.h" #include "Script.h" #include "StringUtilities.h" #include "WebDriverConstants.h" namespace webdriver { LRESULT AsyncScriptExecutor::OnInit(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnInit"; return 0; } LRESULT AsyncScriptExecutor::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnCreate"; CREATESTRUCT* create = reinterpret_cast(lParam); AsyncScriptExecutorThreadContext* context = reinterpret_cast(create->lpCreateParams); this->script_source_code_ = context->script_source; this->script_argument_count_ = context->script_argument_count; this->script_argument_index_ = 0; std::wstring serialized_args = context->serialized_script_args; this->main_element_repository_handle_ = context->main_element_repository_handle; if (serialized_args.size() > 0) { std::string parse_errors; std::stringstream json_stream; json_stream.str(StringUtilities::ToString(serialized_args)); Json::parseFromStream(Json::CharReaderBuilder(), json_stream, &this->script_args_, &parse_errors); if (this->script_args_.isArray()) { this->GetElementIdList(this->script_args_); this->script_argument_count_ = this->script_args_.size(); } } else { this->main_element_repository_handle_ = NULL; this->script_args_ = Json::Value::null; } // Calling vector::resize() is okay here, because the vector // should be empty when Initialize() is called, and the // reallocation of variants shouldn't give us too much of a // negative impact. this->script_arguments_.resize(this->script_argument_count_); this->status_code_ = WD_SUCCESS; this->is_execution_completed_ = false; this->is_listener_attached_ = true; this->element_repository_ = new ElementRepository(); this->polling_script_source_code_ = L""; return 0; } LRESULT AsyncScriptExecutor::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnClose"; this->DestroyWindow(); return 0; } LRESULT AsyncScriptExecutor::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncAtomExecutor::OnDestroy"; delete this->element_repository_; ::PostQuitMessage(0); return 0; } LRESULT AsyncScriptExecutor::OnSetDocument(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnSetDocument"; CComPtr doc; LPSTREAM initializer_payload = reinterpret_cast(lParam); HRESULT hr = ::CoGetInterfaceAndReleaseStream(initializer_payload, IID_IHTMLDocument2, reinterpret_cast(&this->script_host_)); return 0; } LRESULT AsyncScriptExecutor::OnSetElementArgument(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnSetElementArgument"; ElementInfo* info = reinterpret_cast(lParam); std::string element_id = info->element_id; std::vector::iterator item = std::find(this->element_id_list_.begin(), this->element_id_list_.end(), element_id); if (item == this->element_id_list_.end()) { LOG(WARN) << "Invalid element ID sent from main repository: " << element_id; } CComPtr element; ::CoGetInterfaceAndReleaseStream(info->element_stream, IID_IHTMLElement, reinterpret_cast(&element)); delete info; ElementHandle element_handle(new Element(element, NULL, element_id)); this->element_repository_->AddManagedElement(element_handle); this->element_id_list_.erase(item); return WD_SUCCESS; } LRESULT AsyncScriptExecutor::OnGetRequiredElementList(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnGetRequiredElementList"; Json::Value element_id_list(Json::arrayValue); std::vector::const_iterator it = this->element_id_list_.begin(); for (; it != this->element_id_list_.end(); ++it) { element_id_list.append(*it); } Json::StreamWriterBuilder writer; std::string serialized_element_list = Json::writeString(writer, element_id_list); std::string* return_string = reinterpret_cast(lParam); *return_string = serialized_element_list.c_str(); return 0; } LRESULT AsyncScriptExecutor::OnIsExecutionReady(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnIsExecutionReady"; return this->element_id_list_.size() == 0 ? 1 : 0; } LRESULT AsyncScriptExecutor::OnSetPollingScript(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnSetPollingScript"; LPCTSTR polling_script = reinterpret_cast(lParam); std::wstring script(polling_script); this->polling_script_source_code_ = script; return 0; } LRESULT AsyncScriptExecutor::OnExecuteScript(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnExecuteScript"; Script script_to_execute(this->script_host_, this->script_source_code_, this->script_argument_count_); this->status_code_ = script_to_execute.AddArguments(this, this->script_args_); if (this->status_code_ == WD_SUCCESS) { this->status_code_ = script_to_execute.Execute(); } if (!this->is_listener_attached_) { ::PostMessage(this->m_hWnd, WM_CLOSE, NULL, NULL); } else { if (this->status_code_ == WD_SUCCESS) { if (this->polling_script_source_code_.size() > 0) { bool polling_script_succeeded = this->WaitForPollingScript(); if (!polling_script_succeeded) { // The polling script either detected a page reload, or it timed out. // In either case, this script execution is completed. this->is_execution_completed_ = true; return 0; } } else { this->status_code_ = script_to_execute.ConvertResultToJsonValue(this, &this->script_result_); } if (this->element_id_list_.size() > 0) { // There are newly discovered elements to be managed, and they // need to be marshaled back to the main executor thread. Note // that we return without setting execution complete, because // execution isn't done until the marshalling is done. TransferReturnedElements(); return 0; } } else { if (script_to_execute.ResultIsString()) { script_to_execute.ConvertResultToJsonValue(this, &this->script_result_); } } } this->is_execution_completed_ = true; return 0; } LRESULT AsyncScriptExecutor::OnDetachListener(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnDetachListener"; this->is_listener_attached_ = false; return 0; } LRESULT AsyncScriptExecutor::OnIsExecutionComplete(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { return this->is_execution_completed_ ? 1 : 0; } LRESULT AsyncScriptExecutor::OnGetResult(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnGetResult"; // NOTE: We need to tell this window to close itself. If and when marshaling // of the actual variant result to the calling thread is implemented, posting // the message to close the window will have to be moved to the method that // retrieves the marshaled result. if (this->main_element_repository_handle_ != NULL) { Json::Value* result = reinterpret_cast(lParam); *result = this->script_result_; } else { ::PostMessage(this->m_hWnd, WM_CLOSE, NULL, NULL); } return this->status_code_; } LRESULT AsyncScriptExecutor::OnNotifyElementTransferred(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering AsyncScriptExecutor::OnNotifyElementTransferred"; RemappedElementInfo* info = reinterpret_cast(lParam); std::string element_id = info->element_id; std::string original_element_id = info->original_element_id; delete info; this->ReplaceTransferredElementResult(original_element_id, element_id, &this->script_result_); std::vector::const_iterator item = std::find(this->element_id_list_.begin(), this->element_id_list_.end(), original_element_id); this->element_id_list_.erase(item); if (this->element_id_list_.size() == 0) { this->is_execution_completed_ = true; } return WD_SUCCESS; } void AsyncScriptExecutor::ReplaceTransferredElementResult(std::string original_element_id, std::string element_id, Json::Value* result) { if (result->isArray()) { for (Json::ArrayIndex i = 0; i < result->size(); ++i) { this->ReplaceTransferredElementResult(original_element_id, element_id, &((*result)[i])); } } else if (result->isObject()) { if (result->isMember(JSON_ELEMENT_PROPERTY_NAME) && (*result)[JSON_ELEMENT_PROPERTY_NAME] == original_element_id) { (*result)[JSON_ELEMENT_PROPERTY_NAME] = element_id; } else { std::vector member_names = result->getMemberNames(); std::vector::const_iterator it = member_names.begin(); for (; it != member_names.end(); ++it) { this->ReplaceTransferredElementResult(original_element_id, element_id, &((*result)[*it])); } } } } int AsyncScriptExecutor::GetManagedElement(const std::string& element_id, ElementHandle* element_wrapper) const { LOG(TRACE) << "Entering AsyncScriptExecutor::GetManagedElement"; return this->element_repository_->GetManagedElement(element_id, element_wrapper); } bool AsyncScriptExecutor::AddManagedElement(IHTMLElement* element, ElementHandle* element_wrapper) { LOG(TRACE) << "Entering AsyncScriptExecutor::AddManagedElement"; bool is_new_element = this->element_repository_->AddManagedElement(NULL, element, element_wrapper); if (is_new_element) { this->element_id_list_.push_back((*element_wrapper)->element_id()); } return is_new_element; } void AsyncScriptExecutor::RemoveManagedElement(const std::string& element_id) { LOG(TRACE) << "Entering AsyncScriptExecutor::RemoveManagedElement"; this->element_repository_->RemoveManagedElement(element_id); // Simply forward on the request to remove the element from the // main element repository. We shouldn't need to worry about waiting // for the removal to be processed; it should be scheduled to happen // before the next command can arrive. ElementInfo* info = new ElementInfo; info->element_id = element_id.c_str(); ::PostMessage(this->main_element_repository_handle_, WD_ASYNC_SCRIPT_SCHEDULE_REMOVE_MANAGED_ELEMENT, NULL, reinterpret_cast(info)); } void AsyncScriptExecutor::GetElementIdList(const Json::Value& json_object) { LOG(TRACE) << "Entering AsyncScriptExecutor::GetElementIdList"; if (json_object.isArray()) { for (unsigned int i = 0; i < json_object.size(); ++i) { GetElementIdList(json_object[i]); } } else if (json_object.isObject()) { if (json_object.isMember(JSON_ELEMENT_PROPERTY_NAME)) { // Capture the ID of any element in the arg list, and std::string element_id; element_id = json_object[JSON_ELEMENT_PROPERTY_NAME].asString(); this->element_id_list_.push_back(element_id); } else { std::vector property_names = json_object.getMemberNames(); std::vector::const_iterator it = property_names.begin(); for (; it != property_names.end(); ++it) { this->GetElementIdList(json_object[*it]); } } } } void AsyncScriptExecutor::TransferReturnedElements() { LOG(TRACE) << "Entering AsyncScriptExecutor::TransferReturnedElements"; std::vector::const_iterator it = this->element_id_list_.begin(); for (; it != this->element_id_list_.end(); ++it) { std::string element_id = *it; ElementHandle element_handle; this->element_repository_->GetManagedElement(element_id, &element_handle); ElementInfo* info = new ElementInfo; info->element_id = element_id.c_str(); ::CoMarshalInterThreadInterfaceInStream(IID_IHTMLElement, element_handle->element(), &info->element_stream); ::PostMessage(this->main_element_repository_handle_, WD_ASYNC_SCRIPT_TRANSFER_MANAGED_ELEMENT, NULL, reinterpret_cast(info)); } } bool AsyncScriptExecutor::WaitForPollingScript(void) { LOG(TRACE) << "Entering AsyncScriptExecutor::WaitForPollingScript"; Script polling_script(this->script_host_, this->polling_script_source_code_, 0); while (this->is_listener_attached_ && this->status_code_ == WD_SUCCESS) { int polling_status_code = polling_script.Execute(); if (polling_status_code != WD_SUCCESS) { this->status_code_ = EUNEXPECTEDJSERROR; this->script_result_ = "Page reload detected during async script"; } else { Json::Value polling_script_result; polling_script.ConvertResultToJsonValue(this, &polling_script_result); if (!polling_script_result.isObject()) { this->status_code_ = EUNEXPECTEDJSERROR; this->script_result_ = "Polling script did not return expected object"; } if (!polling_script_result.isMember("status")) { this->status_code_ = EUNEXPECTEDJSERROR; this->script_result_ = "Polling script did not return expected object"; } std::string polling_script_status = polling_script_result["status"].asString(); if (polling_script_status == "reload") { this->status_code_ = EUNEXPECTEDJSERROR; this->script_result_ = "Page reload detected during async script"; } if (polling_script_status == "timeout") { this->status_code_ = ESCRIPTTIMEOUT; this->script_result_ = "Timeout expired waiting for async script"; } if (polling_script_status == "complete") { this->status_code_ = WD_SUCCESS; this->script_result_ = polling_script_result["value"]; break; } } } return this->status_code_ == WD_SUCCESS; } unsigned int WINAPI AsyncScriptExecutor::ThreadProc(LPVOID lpParameter) { LOG(TRACE) << "Entering AsyncScriptExecutor::ThreadProc"; AsyncScriptExecutorThreadContext* thread_context = reinterpret_cast(lpParameter); HWND window_handle = thread_context->hwnd; DWORD error = 0; HRESULT hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (FAILED(hr)) { LOGHR(DEBUG, hr) << "COM library initialization has some problem"; } AsyncScriptExecutor async_executor; HWND async_executor_window_handle = async_executor.Create(/*HWND*/ HWND_MESSAGE, /*_U_RECT rect*/ CWindow::rcDefault, /*LPCTSTR szWindowName*/ NULL, /*DWORD dwStyle*/ NULL, /*DWORD dwExStyle*/ NULL, /*_U_MENUorID MenuOrID*/ 0U, /*LPVOID lpCreateParam*/ reinterpret_cast(thread_context)); if (async_executor_window_handle == NULL) { LOGERR(WARN) << "Unable to create new AsyncScriptExecutor"; } MSG msg; ::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); // Return the HWND back through lpParameter, and signal that the // window is ready for messages. thread_context->hwnd = async_executor_window_handle; HANDLE event_handle = ::OpenEvent(EVENT_ALL_ACCESS, FALSE, ASYNC_SCRIPT_EVENT_NAME); if (event_handle != NULL) { ::SetEvent(event_handle); ::CloseHandle(event_handle); } else { LOGERR(DEBUG) << "Unable to signal that window is ready"; } // Run the message loop while (::GetMessage(&msg, NULL, 0, 0) > 0) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return 0; } } // namespace webdriver