Path: blob/trunk/cpp/iedriver/AsyncScriptExecutor.cpp
2867 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the "License");5// you may not use this file except in compliance with the License.6// You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing, software11// distributed under the License is distributed on an "AS IS" BASIS,12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13// See the License for the specific language governing permissions and14// limitations under the License.1516#include "AsyncScriptExecutor.h"1718#include <vector>1920#include "errorcodes.h"21#include "logging.h"2223#include "Element.h"24#include "ElementRepository.h"25#include "Script.h"26#include "StringUtilities.h"27#include "WebDriverConstants.h"2829namespace webdriver {3031LRESULT AsyncScriptExecutor::OnInit(UINT uMsg,32WPARAM wParam,33LPARAM lParam,34BOOL& bHandled) {35LOG(TRACE) << "Entering AsyncScriptExecutor::OnInit";36return 0;37}3839LRESULT AsyncScriptExecutor::OnCreate(UINT uMsg,40WPARAM wParam,41LPARAM lParam,42BOOL& bHandled) {43LOG(TRACE) << "Entering AsyncScriptExecutor::OnCreate";4445CREATESTRUCT* create = reinterpret_cast<CREATESTRUCT*>(lParam);46AsyncScriptExecutorThreadContext* context = reinterpret_cast<AsyncScriptExecutorThreadContext*>(create->lpCreateParams);4748this->script_source_code_ = context->script_source;49this->script_argument_count_ = context->script_argument_count;50this->script_argument_index_ = 0;51std::wstring serialized_args = context->serialized_script_args;52this->main_element_repository_handle_ = context->main_element_repository_handle;53if (serialized_args.size() > 0) {54std::string parse_errors;55std::stringstream json_stream;56json_stream.str(StringUtilities::ToString(serialized_args));57Json::parseFromStream(Json::CharReaderBuilder(),58json_stream,59&this->script_args_,60&parse_errors);6162if (this->script_args_.isArray()) {63this->GetElementIdList(this->script_args_);64this->script_argument_count_ = this->script_args_.size();65}66} else {67this->main_element_repository_handle_ = NULL;68this->script_args_ = Json::Value::null;69}70// Calling vector::resize() is okay here, because the vector71// should be empty when Initialize() is called, and the72// reallocation of variants shouldn't give us too much of a73// negative impact.74this->script_arguments_.resize(this->script_argument_count_);75this->status_code_ = WD_SUCCESS;76this->is_execution_completed_ = false;77this->is_listener_attached_ = true;78this->element_repository_ = new ElementRepository();79this->polling_script_source_code_ = L"";80return 0;81}8283LRESULT AsyncScriptExecutor::OnClose(UINT uMsg,84WPARAM wParam,85LPARAM lParam,86BOOL& bHandled) {87LOG(TRACE) << "Entering AsyncScriptExecutor::OnClose";88this->DestroyWindow();89return 0;90}9192LRESULT AsyncScriptExecutor::OnDestroy(UINT uMsg,93WPARAM wParam,94LPARAM lParam,95BOOL& bHandled) {96LOG(TRACE) << "Entering AsyncAtomExecutor::OnDestroy";97delete this->element_repository_;98::PostQuitMessage(0);99return 0;100}101102LRESULT AsyncScriptExecutor::OnSetDocument(UINT uMsg,103WPARAM wParam,104LPARAM lParam,105BOOL& bHandled) {106LOG(TRACE) << "Entering AsyncScriptExecutor::OnSetDocument";107CComPtr<IHTMLDocument2> doc;108LPSTREAM initializer_payload = reinterpret_cast<LPSTREAM>(lParam);109HRESULT hr = ::CoGetInterfaceAndReleaseStream(initializer_payload,110IID_IHTMLDocument2,111reinterpret_cast<void**>(&this->script_host_));112return 0;113}114115LRESULT AsyncScriptExecutor::OnSetElementArgument(UINT uMsg,116WPARAM wParam,117LPARAM lParam,118BOOL& bHandled) {119LOG(TRACE) << "Entering AsyncScriptExecutor::OnSetElementArgument";120ElementInfo* info = reinterpret_cast<ElementInfo*>(lParam);121std::string element_id = info->element_id;122std::vector<std::string>::iterator item = std::find(this->element_id_list_.begin(),123this->element_id_list_.end(),124element_id);125if (item == this->element_id_list_.end()) {126LOG(WARN) << "Invalid element ID sent from main repository: " << element_id;127}128CComPtr<IHTMLElement> element;129::CoGetInterfaceAndReleaseStream(info->element_stream,130IID_IHTMLElement,131reinterpret_cast<void**>(&element));132delete info;133ElementHandle element_handle(new Element(element, NULL, element_id));134this->element_repository_->AddManagedElement(element_handle);135this->element_id_list_.erase(item);136return WD_SUCCESS;137}138139LRESULT AsyncScriptExecutor::OnGetRequiredElementList(UINT uMsg,140WPARAM wParam,141LPARAM lParam,142BOOL& bHandled) {143LOG(TRACE) << "Entering AsyncScriptExecutor::OnGetRequiredElementList";144Json::Value element_id_list(Json::arrayValue);145std::vector<std::string>::const_iterator it = this->element_id_list_.begin();146for (; it != this->element_id_list_.end(); ++it) {147element_id_list.append(*it);148}149Json::StreamWriterBuilder writer;150std::string serialized_element_list = Json::writeString(writer, element_id_list);151std::string* return_string = reinterpret_cast<std::string*>(lParam);152*return_string = serialized_element_list.c_str();153return 0;154}155156LRESULT AsyncScriptExecutor::OnIsExecutionReady(UINT uMsg,157WPARAM wParam,158LPARAM lParam,159BOOL& bHandled) {160LOG(TRACE) << "Entering AsyncScriptExecutor::OnIsExecutionReady";161return this->element_id_list_.size() == 0 ? 1 : 0;162}163164LRESULT AsyncScriptExecutor::OnSetPollingScript(UINT uMsg,165WPARAM wParam,166LPARAM lParam,167BOOL& bHandled) {168LOG(TRACE) << "Entering AsyncScriptExecutor::OnSetPollingScript";169LPCTSTR polling_script = reinterpret_cast<LPCTSTR>(lParam);170std::wstring script(polling_script);171this->polling_script_source_code_ = script;172return 0;173}174175LRESULT AsyncScriptExecutor::OnExecuteScript(UINT uMsg,176WPARAM wParam,177LPARAM lParam,178BOOL& bHandled) {179LOG(TRACE) << "Entering AsyncScriptExecutor::OnExecuteScript";180Script script_to_execute(this->script_host_,181this->script_source_code_,182this->script_argument_count_);183this->status_code_ = script_to_execute.AddArguments(this,184this->script_args_);185186if (this->status_code_ == WD_SUCCESS) {187this->status_code_ = script_to_execute.Execute();188}189190if (!this->is_listener_attached_) {191::PostMessage(this->m_hWnd, WM_CLOSE, NULL, NULL);192} else {193if (this->status_code_ == WD_SUCCESS) {194if (this->polling_script_source_code_.size() > 0) {195bool polling_script_succeeded = this->WaitForPollingScript();196if (!polling_script_succeeded) {197// The polling script either detected a page reload, or it timed out.198// In either case, this script execution is completed.199this->is_execution_completed_ = true;200return 0;201}202} else {203this->status_code_ = script_to_execute.ConvertResultToJsonValue(this,204&this->script_result_);205}206if (this->element_id_list_.size() > 0) {207// There are newly discovered elements to be managed, and they208// need to be marshaled back to the main executor thread. Note209// that we return without setting execution complete, because210// execution isn't done until the marshalling is done.211TransferReturnedElements();212return 0;213}214} else {215if (script_to_execute.ResultIsString()) {216script_to_execute.ConvertResultToJsonValue(this,217&this->script_result_);218}219}220}221this->is_execution_completed_ = true;222return 0;223}224225LRESULT AsyncScriptExecutor::OnDetachListener(UINT uMsg,226WPARAM wParam,227LPARAM lParam,228BOOL& bHandled) {229LOG(TRACE) << "Entering AsyncScriptExecutor::OnDetachListener";230this->is_listener_attached_ = false;231return 0;232}233234LRESULT AsyncScriptExecutor::OnIsExecutionComplete(UINT uMsg,235WPARAM wParam,236LPARAM lParam,237BOOL& bHandled) {238return this->is_execution_completed_ ? 1 : 0;239}240241LRESULT AsyncScriptExecutor::OnGetResult(UINT uMsg,242WPARAM wParam,243LPARAM lParam,244BOOL& bHandled) {245LOG(TRACE) << "Entering AsyncScriptExecutor::OnGetResult";246// NOTE: We need to tell this window to close itself. If and when marshaling247// of the actual variant result to the calling thread is implemented, posting248// the message to close the window will have to be moved to the method that249// retrieves the marshaled result.250if (this->main_element_repository_handle_ != NULL) {251Json::Value* result = reinterpret_cast<Json::Value*>(lParam);252*result = this->script_result_;253} else {254::PostMessage(this->m_hWnd, WM_CLOSE, NULL, NULL);255}256return this->status_code_;257}258259LRESULT AsyncScriptExecutor::OnNotifyElementTransferred(UINT uMsg,260WPARAM wParam,261LPARAM lParam,262BOOL& bHandled) {263LOG(TRACE) << "Entering AsyncScriptExecutor::OnNotifyElementTransferred";264RemappedElementInfo* info = reinterpret_cast<RemappedElementInfo*>(lParam);265std::string element_id = info->element_id;266std::string original_element_id = info->original_element_id;267delete info;268this->ReplaceTransferredElementResult(original_element_id, element_id, &this->script_result_);269std::vector<std::string>::const_iterator item = std::find(this->element_id_list_.begin(),270this->element_id_list_.end(),271original_element_id);272this->element_id_list_.erase(item);273if (this->element_id_list_.size() == 0) {274this->is_execution_completed_ = true;275}276return WD_SUCCESS;277}278void AsyncScriptExecutor::ReplaceTransferredElementResult(std::string original_element_id,279std::string element_id,280Json::Value* result) {281if (result->isArray()) {282for (Json::ArrayIndex i = 0; i < result->size(); ++i) {283this->ReplaceTransferredElementResult(original_element_id,284element_id,285&((*result)[i]));286}287} else if (result->isObject()) {288if (result->isMember(JSON_ELEMENT_PROPERTY_NAME) &&289(*result)[JSON_ELEMENT_PROPERTY_NAME] == original_element_id) {290(*result)[JSON_ELEMENT_PROPERTY_NAME] = element_id;291} else {292std::vector<std::string> member_names = result->getMemberNames();293std::vector<std::string>::const_iterator it = member_names.begin();294for (; it != member_names.end(); ++it) {295this->ReplaceTransferredElementResult(original_element_id,296element_id,297&((*result)[*it]));298}299}300}301}302303int AsyncScriptExecutor::GetManagedElement(const std::string& element_id,304ElementHandle* element_wrapper) const {305LOG(TRACE) << "Entering AsyncScriptExecutor::GetManagedElement";306return this->element_repository_->GetManagedElement(element_id,307element_wrapper);308}309310bool AsyncScriptExecutor::AddManagedElement(IHTMLElement* element,311ElementHandle* element_wrapper) {312LOG(TRACE) << "Entering AsyncScriptExecutor::AddManagedElement";313bool is_new_element = this->element_repository_->AddManagedElement(NULL,314element,315element_wrapper);316if (is_new_element) {317this->element_id_list_.push_back((*element_wrapper)->element_id());318}319return is_new_element;320}321322void AsyncScriptExecutor::RemoveManagedElement(const std::string& element_id) {323LOG(TRACE) << "Entering AsyncScriptExecutor::RemoveManagedElement";324this->element_repository_->RemoveManagedElement(element_id);325326// Simply forward on the request to remove the element from the327// main element repository. We shouldn't need to worry about waiting328// for the removal to be processed; it should be scheduled to happen329// before the next command can arrive.330ElementInfo* info = new ElementInfo;331info->element_id = element_id.c_str();332::PostMessage(this->main_element_repository_handle_,333WD_ASYNC_SCRIPT_SCHEDULE_REMOVE_MANAGED_ELEMENT,334NULL,335reinterpret_cast<LPARAM>(info));336}337338void AsyncScriptExecutor::GetElementIdList(const Json::Value& json_object) {339LOG(TRACE) << "Entering AsyncScriptExecutor::GetElementIdList";340if (json_object.isArray()) {341for (unsigned int i = 0; i < json_object.size(); ++i) {342GetElementIdList(json_object[i]);343}344} else if (json_object.isObject()) {345if (json_object.isMember(JSON_ELEMENT_PROPERTY_NAME)) {346// Capture the ID of any element in the arg list, and347std::string element_id;348element_id = json_object[JSON_ELEMENT_PROPERTY_NAME].asString();349this->element_id_list_.push_back(element_id);350} else {351std::vector<std::string> property_names = json_object.getMemberNames();352std::vector<std::string>::const_iterator it = property_names.begin();353for (; it != property_names.end(); ++it) {354this->GetElementIdList(json_object[*it]);355}356}357}358}359360void AsyncScriptExecutor::TransferReturnedElements() {361LOG(TRACE) << "Entering AsyncScriptExecutor::TransferReturnedElements";362std::vector<std::string>::const_iterator it = this->element_id_list_.begin();363for (; it != this->element_id_list_.end(); ++it) {364std::string element_id = *it;365ElementHandle element_handle;366this->element_repository_->GetManagedElement(element_id,367&element_handle);368ElementInfo* info = new ElementInfo;369info->element_id = element_id.c_str();370::CoMarshalInterThreadInterfaceInStream(IID_IHTMLElement,371element_handle->element(),372&info->element_stream);373::PostMessage(this->main_element_repository_handle_,374WD_ASYNC_SCRIPT_TRANSFER_MANAGED_ELEMENT,375NULL,376reinterpret_cast<LPARAM>(info));377}378}379380bool AsyncScriptExecutor::WaitForPollingScript(void) {381LOG(TRACE) << "Entering AsyncScriptExecutor::WaitForPollingScript";382Script polling_script(this->script_host_, this->polling_script_source_code_, 0);383while (this->is_listener_attached_ && this->status_code_ == WD_SUCCESS) {384int polling_status_code = polling_script.Execute();385if (polling_status_code != WD_SUCCESS) {386this->status_code_ = EUNEXPECTEDJSERROR;387this->script_result_ = "Page reload detected during async script";388} else {389Json::Value polling_script_result;390polling_script.ConvertResultToJsonValue(this, &polling_script_result);391if (!polling_script_result.isObject()) {392this->status_code_ = EUNEXPECTEDJSERROR;393this->script_result_ = "Polling script did not return expected object";394}395if (!polling_script_result.isMember("status")) {396this->status_code_ = EUNEXPECTEDJSERROR;397this->script_result_ = "Polling script did not return expected object";398}399std::string polling_script_status = polling_script_result["status"].asString();400if (polling_script_status == "reload") {401this->status_code_ = EUNEXPECTEDJSERROR;402this->script_result_ = "Page reload detected during async script";403}404if (polling_script_status == "timeout") {405this->status_code_ = ESCRIPTTIMEOUT;406this->script_result_ = "Timeout expired waiting for async script";407}408if (polling_script_status == "complete") {409this->status_code_ = WD_SUCCESS;410this->script_result_ = polling_script_result["value"];411break;412}413}414}415return this->status_code_ == WD_SUCCESS;416}417418unsigned int WINAPI AsyncScriptExecutor::ThreadProc(LPVOID lpParameter) {419LOG(TRACE) << "Entering AsyncScriptExecutor::ThreadProc";420421AsyncScriptExecutorThreadContext* thread_context = reinterpret_cast<AsyncScriptExecutorThreadContext*>(lpParameter);422HWND window_handle = thread_context->hwnd;423424DWORD error = 0;425HRESULT hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);426if (FAILED(hr)) {427LOGHR(DEBUG, hr) << "COM library initialization has some problem";428}429430AsyncScriptExecutor async_executor;431HWND async_executor_window_handle = async_executor.Create(/*HWND*/ HWND_MESSAGE,432/*_U_RECT rect*/ CWindow::rcDefault,433/*LPCTSTR szWindowName*/ NULL,434/*DWORD dwStyle*/ NULL,435/*DWORD dwExStyle*/ NULL,436/*_U_MENUorID MenuOrID*/ 0U,437/*LPVOID lpCreateParam*/ reinterpret_cast<LPVOID*>(thread_context));438if (async_executor_window_handle == NULL) {439LOGERR(WARN) << "Unable to create new AsyncScriptExecutor";440}441442MSG msg;443::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);444445// Return the HWND back through lpParameter, and signal that the446// window is ready for messages.447thread_context->hwnd = async_executor_window_handle;448HANDLE event_handle = ::OpenEvent(EVENT_ALL_ACCESS,449FALSE,450ASYNC_SCRIPT_EVENT_NAME);451if (event_handle != NULL) {452::SetEvent(event_handle);453::CloseHandle(event_handle);454} else {455LOGERR(DEBUG) << "Unable to signal that window is ready";456}457458// Run the message loop459while (::GetMessage(&msg, NULL, 0, 0) > 0) {460::TranslateMessage(&msg);461::DispatchMessage(&msg);462}463464::CoUninitialize();465return 0;466}467468} // namespace webdriver469470471