Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/cpp/iedriver/AsyncScriptExecutor.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
#include "AsyncScriptExecutor.h"
18
19
#include <vector>
20
21
#include "errorcodes.h"
22
#include "logging.h"
23
24
#include "Element.h"
25
#include "ElementRepository.h"
26
#include "Script.h"
27
#include "StringUtilities.h"
28
#include "WebDriverConstants.h"
29
30
namespace webdriver {
31
32
LRESULT AsyncScriptExecutor::OnInit(UINT uMsg,
33
WPARAM wParam,
34
LPARAM lParam,
35
BOOL& bHandled) {
36
LOG(TRACE) << "Entering AsyncScriptExecutor::OnInit";
37
return 0;
38
}
39
40
LRESULT AsyncScriptExecutor::OnCreate(UINT uMsg,
41
WPARAM wParam,
42
LPARAM lParam,
43
BOOL& bHandled) {
44
LOG(TRACE) << "Entering AsyncScriptExecutor::OnCreate";
45
46
CREATESTRUCT* create = reinterpret_cast<CREATESTRUCT*>(lParam);
47
AsyncScriptExecutorThreadContext* context = reinterpret_cast<AsyncScriptExecutorThreadContext*>(create->lpCreateParams);
48
49
this->script_source_code_ = context->script_source;
50
this->script_argument_count_ = context->script_argument_count;
51
this->script_argument_index_ = 0;
52
std::wstring serialized_args = context->serialized_script_args;
53
this->main_element_repository_handle_ = context->main_element_repository_handle;
54
if (serialized_args.size() > 0) {
55
std::string parse_errors;
56
std::stringstream json_stream;
57
json_stream.str(StringUtilities::ToString(serialized_args));
58
Json::parseFromStream(Json::CharReaderBuilder(),
59
json_stream,
60
&this->script_args_,
61
&parse_errors);
62
63
if (this->script_args_.isArray()) {
64
this->GetElementIdList(this->script_args_);
65
this->script_argument_count_ = this->script_args_.size();
66
}
67
} else {
68
this->main_element_repository_handle_ = NULL;
69
this->script_args_ = Json::Value::null;
70
}
71
// Calling vector::resize() is okay here, because the vector
72
// should be empty when Initialize() is called, and the
73
// reallocation of variants shouldn't give us too much of a
74
// negative impact.
75
this->script_arguments_.resize(this->script_argument_count_);
76
this->status_code_ = WD_SUCCESS;
77
this->is_execution_completed_ = false;
78
this->is_listener_attached_ = true;
79
this->element_repository_ = new ElementRepository();
80
this->polling_script_source_code_ = L"";
81
return 0;
82
}
83
84
LRESULT AsyncScriptExecutor::OnClose(UINT uMsg,
85
WPARAM wParam,
86
LPARAM lParam,
87
BOOL& bHandled) {
88
LOG(TRACE) << "Entering AsyncScriptExecutor::OnClose";
89
this->DestroyWindow();
90
return 0;
91
}
92
93
LRESULT AsyncScriptExecutor::OnDestroy(UINT uMsg,
94
WPARAM wParam,
95
LPARAM lParam,
96
BOOL& bHandled) {
97
LOG(TRACE) << "Entering AsyncAtomExecutor::OnDestroy";
98
delete this->element_repository_;
99
::PostQuitMessage(0);
100
return 0;
101
}
102
103
LRESULT AsyncScriptExecutor::OnSetDocument(UINT uMsg,
104
WPARAM wParam,
105
LPARAM lParam,
106
BOOL& bHandled) {
107
LOG(TRACE) << "Entering AsyncScriptExecutor::OnSetDocument";
108
CComPtr<IHTMLDocument2> doc;
109
LPSTREAM initializer_payload = reinterpret_cast<LPSTREAM>(lParam);
110
HRESULT hr = ::CoGetInterfaceAndReleaseStream(initializer_payload,
111
IID_IHTMLDocument2,
112
reinterpret_cast<void**>(&this->script_host_));
113
return 0;
114
}
115
116
LRESULT AsyncScriptExecutor::OnSetElementArgument(UINT uMsg,
117
WPARAM wParam,
118
LPARAM lParam,
119
BOOL& bHandled) {
120
LOG(TRACE) << "Entering AsyncScriptExecutor::OnSetElementArgument";
121
ElementInfo* info = reinterpret_cast<ElementInfo*>(lParam);
122
std::string element_id = info->element_id;
123
std::vector<std::string>::iterator item = std::find(this->element_id_list_.begin(),
124
this->element_id_list_.end(),
125
element_id);
126
if (item == this->element_id_list_.end()) {
127
LOG(WARN) << "Invalid element ID sent from main repository: " << element_id;
128
}
129
CComPtr<IHTMLElement> element;
130
::CoGetInterfaceAndReleaseStream(info->element_stream,
131
IID_IHTMLElement,
132
reinterpret_cast<void**>(&element));
133
delete info;
134
ElementHandle element_handle(new Element(element, NULL, element_id));
135
this->element_repository_->AddManagedElement(element_handle);
136
this->element_id_list_.erase(item);
137
return WD_SUCCESS;
138
}
139
140
LRESULT AsyncScriptExecutor::OnGetRequiredElementList(UINT uMsg,
141
WPARAM wParam,
142
LPARAM lParam,
143
BOOL& bHandled) {
144
LOG(TRACE) << "Entering AsyncScriptExecutor::OnGetRequiredElementList";
145
Json::Value element_id_list(Json::arrayValue);
146
std::vector<std::string>::const_iterator it = this->element_id_list_.begin();
147
for (; it != this->element_id_list_.end(); ++it) {
148
element_id_list.append(*it);
149
}
150
Json::StreamWriterBuilder writer;
151
std::string serialized_element_list = Json::writeString(writer, element_id_list);
152
std::string* return_string = reinterpret_cast<std::string*>(lParam);
153
*return_string = serialized_element_list.c_str();
154
return 0;
155
}
156
157
LRESULT AsyncScriptExecutor::OnIsExecutionReady(UINT uMsg,
158
WPARAM wParam,
159
LPARAM lParam,
160
BOOL& bHandled) {
161
LOG(TRACE) << "Entering AsyncScriptExecutor::OnIsExecutionReady";
162
return this->element_id_list_.size() == 0 ? 1 : 0;
163
}
164
165
LRESULT AsyncScriptExecutor::OnSetPollingScript(UINT uMsg,
166
WPARAM wParam,
167
LPARAM lParam,
168
BOOL& bHandled) {
169
LOG(TRACE) << "Entering AsyncScriptExecutor::OnSetPollingScript";
170
LPCTSTR polling_script = reinterpret_cast<LPCTSTR>(lParam);
171
std::wstring script(polling_script);
172
this->polling_script_source_code_ = script;
173
return 0;
174
}
175
176
LRESULT AsyncScriptExecutor::OnExecuteScript(UINT uMsg,
177
WPARAM wParam,
178
LPARAM lParam,
179
BOOL& bHandled) {
180
LOG(TRACE) << "Entering AsyncScriptExecutor::OnExecuteScript";
181
Script script_to_execute(this->script_host_,
182
this->script_source_code_,
183
this->script_argument_count_);
184
this->status_code_ = script_to_execute.AddArguments(this,
185
this->script_args_);
186
187
if (this->status_code_ == WD_SUCCESS) {
188
this->status_code_ = script_to_execute.Execute();
189
}
190
191
if (!this->is_listener_attached_) {
192
::PostMessage(this->m_hWnd, WM_CLOSE, NULL, NULL);
193
} else {
194
if (this->status_code_ == WD_SUCCESS) {
195
if (this->polling_script_source_code_.size() > 0) {
196
bool polling_script_succeeded = this->WaitForPollingScript();
197
if (!polling_script_succeeded) {
198
// The polling script either detected a page reload, or it timed out.
199
// In either case, this script execution is completed.
200
this->is_execution_completed_ = true;
201
return 0;
202
}
203
} else {
204
this->status_code_ = script_to_execute.ConvertResultToJsonValue(this,
205
&this->script_result_);
206
}
207
if (this->element_id_list_.size() > 0) {
208
// There are newly discovered elements to be managed, and they
209
// need to be marshaled back to the main executor thread. Note
210
// that we return without setting execution complete, because
211
// execution isn't done until the marshalling is done.
212
TransferReturnedElements();
213
return 0;
214
}
215
} else {
216
if (script_to_execute.ResultIsString()) {
217
script_to_execute.ConvertResultToJsonValue(this,
218
&this->script_result_);
219
}
220
}
221
}
222
this->is_execution_completed_ = true;
223
return 0;
224
}
225
226
LRESULT AsyncScriptExecutor::OnDetachListener(UINT uMsg,
227
WPARAM wParam,
228
LPARAM lParam,
229
BOOL& bHandled) {
230
LOG(TRACE) << "Entering AsyncScriptExecutor::OnDetachListener";
231
this->is_listener_attached_ = false;
232
return 0;
233
}
234
235
LRESULT AsyncScriptExecutor::OnIsExecutionComplete(UINT uMsg,
236
WPARAM wParam,
237
LPARAM lParam,
238
BOOL& bHandled) {
239
return this->is_execution_completed_ ? 1 : 0;
240
}
241
242
LRESULT AsyncScriptExecutor::OnGetResult(UINT uMsg,
243
WPARAM wParam,
244
LPARAM lParam,
245
BOOL& bHandled) {
246
LOG(TRACE) << "Entering AsyncScriptExecutor::OnGetResult";
247
// NOTE: We need to tell this window to close itself. If and when marshaling
248
// of the actual variant result to the calling thread is implemented, posting
249
// the message to close the window will have to be moved to the method that
250
// retrieves the marshaled result.
251
if (this->main_element_repository_handle_ != NULL) {
252
Json::Value* result = reinterpret_cast<Json::Value*>(lParam);
253
*result = this->script_result_;
254
} else {
255
::PostMessage(this->m_hWnd, WM_CLOSE, NULL, NULL);
256
}
257
return this->status_code_;
258
}
259
260
LRESULT AsyncScriptExecutor::OnNotifyElementTransferred(UINT uMsg,
261
WPARAM wParam,
262
LPARAM lParam,
263
BOOL& bHandled) {
264
LOG(TRACE) << "Entering AsyncScriptExecutor::OnNotifyElementTransferred";
265
RemappedElementInfo* info = reinterpret_cast<RemappedElementInfo*>(lParam);
266
std::string element_id = info->element_id;
267
std::string original_element_id = info->original_element_id;
268
delete info;
269
this->ReplaceTransferredElementResult(original_element_id, element_id, &this->script_result_);
270
std::vector<std::string>::const_iterator item = std::find(this->element_id_list_.begin(),
271
this->element_id_list_.end(),
272
original_element_id);
273
this->element_id_list_.erase(item);
274
if (this->element_id_list_.size() == 0) {
275
this->is_execution_completed_ = true;
276
}
277
return WD_SUCCESS;
278
}
279
void AsyncScriptExecutor::ReplaceTransferredElementResult(std::string original_element_id,
280
std::string element_id,
281
Json::Value* result) {
282
if (result->isArray()) {
283
for (Json::ArrayIndex i = 0; i < result->size(); ++i) {
284
this->ReplaceTransferredElementResult(original_element_id,
285
element_id,
286
&((*result)[i]));
287
}
288
} else if (result->isObject()) {
289
if (result->isMember(JSON_ELEMENT_PROPERTY_NAME) &&
290
(*result)[JSON_ELEMENT_PROPERTY_NAME] == original_element_id) {
291
(*result)[JSON_ELEMENT_PROPERTY_NAME] = element_id;
292
} else {
293
std::vector<std::string> member_names = result->getMemberNames();
294
std::vector<std::string>::const_iterator it = member_names.begin();
295
for (; it != member_names.end(); ++it) {
296
this->ReplaceTransferredElementResult(original_element_id,
297
element_id,
298
&((*result)[*it]));
299
}
300
}
301
}
302
}
303
304
int AsyncScriptExecutor::GetManagedElement(const std::string& element_id,
305
ElementHandle* element_wrapper) const {
306
LOG(TRACE) << "Entering AsyncScriptExecutor::GetManagedElement";
307
return this->element_repository_->GetManagedElement(element_id,
308
element_wrapper);
309
}
310
311
bool AsyncScriptExecutor::AddManagedElement(IHTMLElement* element,
312
ElementHandle* element_wrapper) {
313
LOG(TRACE) << "Entering AsyncScriptExecutor::AddManagedElement";
314
bool is_new_element = this->element_repository_->AddManagedElement(NULL,
315
element,
316
element_wrapper);
317
if (is_new_element) {
318
this->element_id_list_.push_back((*element_wrapper)->element_id());
319
}
320
return is_new_element;
321
}
322
323
void AsyncScriptExecutor::RemoveManagedElement(const std::string& element_id) {
324
LOG(TRACE) << "Entering AsyncScriptExecutor::RemoveManagedElement";
325
this->element_repository_->RemoveManagedElement(element_id);
326
327
// Simply forward on the request to remove the element from the
328
// main element repository. We shouldn't need to worry about waiting
329
// for the removal to be processed; it should be scheduled to happen
330
// before the next command can arrive.
331
ElementInfo* info = new ElementInfo;
332
info->element_id = element_id.c_str();
333
::PostMessage(this->main_element_repository_handle_,
334
WD_ASYNC_SCRIPT_SCHEDULE_REMOVE_MANAGED_ELEMENT,
335
NULL,
336
reinterpret_cast<LPARAM>(info));
337
}
338
339
void AsyncScriptExecutor::GetElementIdList(const Json::Value& json_object) {
340
LOG(TRACE) << "Entering AsyncScriptExecutor::GetElementIdList";
341
if (json_object.isArray()) {
342
for (unsigned int i = 0; i < json_object.size(); ++i) {
343
GetElementIdList(json_object[i]);
344
}
345
} else if (json_object.isObject()) {
346
if (json_object.isMember(JSON_ELEMENT_PROPERTY_NAME)) {
347
// Capture the ID of any element in the arg list, and
348
std::string element_id;
349
element_id = json_object[JSON_ELEMENT_PROPERTY_NAME].asString();
350
this->element_id_list_.push_back(element_id);
351
} else {
352
std::vector<std::string> property_names = json_object.getMemberNames();
353
std::vector<std::string>::const_iterator it = property_names.begin();
354
for (; it != property_names.end(); ++it) {
355
this->GetElementIdList(json_object[*it]);
356
}
357
}
358
}
359
}
360
361
void AsyncScriptExecutor::TransferReturnedElements() {
362
LOG(TRACE) << "Entering AsyncScriptExecutor::TransferReturnedElements";
363
std::vector<std::string>::const_iterator it = this->element_id_list_.begin();
364
for (; it != this->element_id_list_.end(); ++it) {
365
std::string element_id = *it;
366
ElementHandle element_handle;
367
this->element_repository_->GetManagedElement(element_id,
368
&element_handle);
369
ElementInfo* info = new ElementInfo;
370
info->element_id = element_id.c_str();
371
::CoMarshalInterThreadInterfaceInStream(IID_IHTMLElement,
372
element_handle->element(),
373
&info->element_stream);
374
::PostMessage(this->main_element_repository_handle_,
375
WD_ASYNC_SCRIPT_TRANSFER_MANAGED_ELEMENT,
376
NULL,
377
reinterpret_cast<LPARAM>(info));
378
}
379
}
380
381
bool AsyncScriptExecutor::WaitForPollingScript(void) {
382
LOG(TRACE) << "Entering AsyncScriptExecutor::WaitForPollingScript";
383
Script polling_script(this->script_host_, this->polling_script_source_code_, 0);
384
while (this->is_listener_attached_ && this->status_code_ == WD_SUCCESS) {
385
int polling_status_code = polling_script.Execute();
386
if (polling_status_code != WD_SUCCESS) {
387
this->status_code_ = EUNEXPECTEDJSERROR;
388
this->script_result_ = "Page reload detected during async script";
389
} else {
390
Json::Value polling_script_result;
391
polling_script.ConvertResultToJsonValue(this, &polling_script_result);
392
if (!polling_script_result.isObject()) {
393
this->status_code_ = EUNEXPECTEDJSERROR;
394
this->script_result_ = "Polling script did not return expected object";
395
}
396
if (!polling_script_result.isMember("status")) {
397
this->status_code_ = EUNEXPECTEDJSERROR;
398
this->script_result_ = "Polling script did not return expected object";
399
}
400
std::string polling_script_status = polling_script_result["status"].asString();
401
if (polling_script_status == "reload") {
402
this->status_code_ = EUNEXPECTEDJSERROR;
403
this->script_result_ = "Page reload detected during async script";
404
}
405
if (polling_script_status == "timeout") {
406
this->status_code_ = ESCRIPTTIMEOUT;
407
this->script_result_ = "Timeout expired waiting for async script";
408
}
409
if (polling_script_status == "complete") {
410
this->status_code_ = WD_SUCCESS;
411
this->script_result_ = polling_script_result["value"];
412
break;
413
}
414
}
415
}
416
return this->status_code_ == WD_SUCCESS;
417
}
418
419
unsigned int WINAPI AsyncScriptExecutor::ThreadProc(LPVOID lpParameter) {
420
LOG(TRACE) << "Entering AsyncScriptExecutor::ThreadProc";
421
422
AsyncScriptExecutorThreadContext* thread_context = reinterpret_cast<AsyncScriptExecutorThreadContext*>(lpParameter);
423
HWND window_handle = thread_context->hwnd;
424
425
DWORD error = 0;
426
HRESULT hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
427
if (FAILED(hr)) {
428
LOGHR(DEBUG, hr) << "COM library initialization has some problem";
429
}
430
431
AsyncScriptExecutor async_executor;
432
HWND async_executor_window_handle = async_executor.Create(/*HWND*/ HWND_MESSAGE,
433
/*_U_RECT rect*/ CWindow::rcDefault,
434
/*LPCTSTR szWindowName*/ NULL,
435
/*DWORD dwStyle*/ NULL,
436
/*DWORD dwExStyle*/ NULL,
437
/*_U_MENUorID MenuOrID*/ 0U,
438
/*LPVOID lpCreateParam*/ reinterpret_cast<LPVOID*>(thread_context));
439
if (async_executor_window_handle == NULL) {
440
LOGERR(WARN) << "Unable to create new AsyncScriptExecutor";
441
}
442
443
MSG msg;
444
::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
445
446
// Return the HWND back through lpParameter, and signal that the
447
// window is ready for messages.
448
thread_context->hwnd = async_executor_window_handle;
449
HANDLE event_handle = ::OpenEvent(EVENT_ALL_ACCESS,
450
FALSE,
451
ASYNC_SCRIPT_EVENT_NAME);
452
if (event_handle != NULL) {
453
::SetEvent(event_handle);
454
::CloseHandle(event_handle);
455
} else {
456
LOGERR(DEBUG) << "Unable to signal that window is ready";
457
}
458
459
// Run the message loop
460
while (::GetMessage(&msg, NULL, 0, 0) > 0) {
461
::TranslateMessage(&msg);
462
::DispatchMessage(&msg);
463
}
464
465
::CoUninitialize();
466
return 0;
467
}
468
469
} // namespace webdriver
470
471