blob: 0a6e70093ff09a8ecf3960c1d60ab683c4794322 [file] [log] [blame]
#include "StdAfx.h"
#include "ScriptWrapper.h"
#include "BrowserManager.h"
#include "logging.h"
namespace webdriver {
ScriptWrapper::ScriptWrapper(IHTMLDocument2 *document, std::wstring script, unsigned long argument_count) {
this->script_engine_host_ = document;
this->script_ = script;
this->argument_count_ = argument_count;
this->current_arg_index_ = 0;
SAFEARRAYBOUND argument_bounds;
argument_bounds.lLbound = 0;
argument_bounds.cElements = this->argument_count_;
this->argument_array_ = ::SafeArrayCreate(VT_VARIANT, 1, &argument_bounds);
::VariantInit(&this->result_);
}
ScriptWrapper::~ScriptWrapper(void) {
::SafeArrayDestroy(this->argument_array_);
}
void ScriptWrapper::AddArgument(const std::wstring& argument) {
CComVariant dest_argument(argument.c_str());
this->AddArgument(dest_argument);
}
void ScriptWrapper::AddArgument(const int argument) {
CComVariant dest_argument((long)argument);
this->AddArgument(dest_argument);
}
void ScriptWrapper::AddArgument(const double argument) {
CComVariant dest_argument(argument);
this->AddArgument(dest_argument);
}
void ScriptWrapper::AddArgument(const bool argument) {
CComVariant dest_argument(argument);
this->AddArgument(dest_argument);
}
void ScriptWrapper::AddArgument(std::tr1::shared_ptr<ElementWrapper> argument) {
this->AddArgument(argument->element());
}
void ScriptWrapper::AddArgument(IHTMLElement *argument) {
CComVariant dest_argument(argument);
this->AddArgument(dest_argument);
}
void ScriptWrapper::AddArgument(VARIANT argument) {
::SafeArrayPutElement(this->argument_array_, &this->current_arg_index_, &argument);
++this->current_arg_index_;
}
bool ScriptWrapper::ResultIsString() {
return this->result_.vt == VT_BSTR;
}
bool ScriptWrapper::ResultIsInteger() {
return this->result_.vt == VT_I4 || this->result_.vt == VT_I8;
}
bool ScriptWrapper::ResultIsDouble() {
return this->result_.vt == VT_R4 || this->result_.vt == VT_R8;
}
bool ScriptWrapper::ResultIsBoolean() {
return this->result_.vt == VT_BOOL;
}
bool ScriptWrapper::ResultIsEmpty() {
return this->result_.vt == VT_EMPTY;
}
bool ScriptWrapper::ResultIsIDispatch() {
return this->result_.vt == VT_DISPATCH;
}
bool ScriptWrapper::ResultIsElementCollection() {
if (this->result_.vt == VT_DISPATCH) {
CComQIPtr<IHTMLElementCollection> is_collection(this->result_.pdispVal);
if (is_collection) {
return true;
}
}
return false;
}
bool ScriptWrapper::ResultIsElement() {
if (this->result_.vt == VT_DISPATCH) {
CComQIPtr<IHTMLElement> is_element(this->result_.pdispVal);
if (is_element) {
return true;
}
}
return false;
}
bool ScriptWrapper::ResultIsArray() {
std::wstring type_name = this->GetResultObjectTypeName();
// If the name is DispStaticNodeList, we can be pretty sure it's an array
// (or at least has array semantics). It is unclear to what extent checking
// for DispStaticNodeList is supported behaviour.
if (type_name == L"DispStaticNodeList") {
return true;
}
// If the name is JScriptTypeInfo then this *may* be a Javascript array.
// Note that strictly speaking, to determine if the result is *actually*
// a JavaScript array object, we should also be testing to see if
// propertyIsEnumerable('length') == false, but that does not find the
// array-like objects returned by some of the calls we make to the Google
// Closure library.
// IMPORTANT: Using this script, user-defined objects with a length
// property defined will be seen as arrays instead of objects.
if (type_name == L"JScriptTypeInfo") {
const std::wstring script = L"(function() { return function(){ return arguments[0] && arguments[0].hasOwnProperty('length') && typeof arguments[0] === 'object' && typeof arguments[0].length === 'number';};})();";
ScriptWrapper is_array_wrapper(this->script_engine_host_, script, 1);
is_array_wrapper.AddArgument(this->result_);
is_array_wrapper.Execute();
return is_array_wrapper.result().boolVal == VARIANT_TRUE;
}
return false;
}
bool ScriptWrapper::ResultIsObject() {
std::wstring type_name = this->GetResultObjectTypeName();
if (type_name == L"JScriptTypeInfo") {
return true;
}
return false;
}
int ScriptWrapper::Execute() {
VARIANT result;
CComVariant temp_function;
if (!this->CreateAnonymousFunction(&temp_function)) {
// Debug level since this is normally the point we find out that
// a page refresh has occured. *sigh*
// LOG(DEBUG) << "Cannot create anonymous function: " << _bstr_t(script) << endl;
return EUNEXPECTEDJSERROR;
}
if (temp_function.vt != VT_DISPATCH) {
// No return value that we care about
::VariantClear(&result);
result.vt = VT_EMPTY;
return SUCCESS;
}
// Grab the "call" method out of the returned function
DISPID call_member_id;
OLECHAR FAR* call_member_name = L"call";
HRESULT hr = temp_function.pdispVal->GetIDsOfNames(IID_NULL, &call_member_name, 1, LOCALE_USER_DEFAULT, &call_member_id);
if (FAILED(hr)) {
// LOGHR(DEBUG, hr) << "Cannot locate call method on anonymous function: " << _bstr_t(script) << endl;
return EUNEXPECTEDJSERROR;
}
DISPPARAMS call_parameters = { 0 };
memset(&call_parameters, 0, sizeof call_parameters);
long lower = 0;
::SafeArrayGetLBound(this->argument_array_, 1, &lower);
long upper = 0;
::SafeArrayGetUBound(this->argument_array_, 1, &upper);
long nargs = 1 + upper - lower;
call_parameters.cArgs = nargs + 1;
CComPtr<IHTMLWindow2> win;
hr = this->script_engine_host_->get_parentWindow(&win);
if (FAILED(hr)) {
LOGHR(WARN, hr) << "Cannot get parent window";
return EUNEXPECTEDJSERROR;
}
_variant_t *vargs = new _variant_t[nargs + 1];
::VariantCopy(&(vargs[nargs]), &CComVariant(win));
long index;
for (int i = 0; i < nargs; i++) {
index = i;
CComVariant v;
::SafeArrayGetElement(this->argument_array_, &index, (void*) &v);
::VariantCopy(&(vargs[nargs - 1 - i]), &v);
}
call_parameters.rgvarg = vargs;
int return_code = SUCCESS;
EXCEPINFO exception;
memset(&exception, 0, sizeof exception);
hr = temp_function.pdispVal->Invoke(call_member_id, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &call_parameters,
&result,
&exception, 0);
if (FAILED(hr)) {
if (DISP_E_EXCEPTION == hr) {
CComBSTR error_description(exception.bstrDescription ? exception.bstrDescription : L"EUNEXPECTEDJSERROR");
LOG(INFO) << "Exception message was: " << error_description;
} else {
// LOGHR(DEBUG, hr) << "Failed to execute: " << _bstr_t(script);
}
::VariantClear(&result);
result.vt = VT_USERDEFINED;
if (exception.bstrDescription != NULL) {
result.bstrVal = ::SysAllocStringByteLen((char*)exception.bstrDescription, ::SysStringByteLen(exception.bstrDescription));
} else {
result.bstrVal = ::SysAllocStringByteLen(NULL, 0);
}
return_code = EUNEXPECTEDJSERROR;
}
// If the script returned an IHTMLElement, we need to copy it to make it valid.
if(VT_DISPATCH == result.vt) {
CComQIPtr<IHTMLElement> element(result.pdispVal);
if(element) {
IHTMLElement* &dom_element = * (IHTMLElement**) &(result.pdispVal);
element.CopyTo(&dom_element);
}
}
this->result_ = result;
delete[] vargs;
return return_code;
}
int ScriptWrapper::ConvertResultToJsonValue(BrowserManager *manager, Json::Value *value) {
int status_code = SUCCESS;
if (this->ResultIsString()) {
std::string string_value("");
if (this->result_.bstrVal) {
string_value = CW2A(this->result_.bstrVal, CP_UTF8);
}
*value = string_value;
} else if (this->ResultIsInteger()) {
*value = this->result_.lVal;
} else if (this->ResultIsDouble()) {
*value = this->result_.dblVal;
} else if (this->ResultIsBoolean()) {
*value = this->result_.boolVal == VARIANT_TRUE;
} else if (this->ResultIsEmpty()) {
*value = Json::Value::null;
} else if (this->ResultIsIDispatch()) {
if (this->ResultIsArray() || this->ResultIsElementCollection()) {
Json::Value result_array(Json::arrayValue);
long length = 0;
status_code = this->GetArrayLength(&length);
for (long i = 0; i < length; ++i) {
Json::Value array_item_result;
int array_item_status = this->GetArrayItem(manager, i, &array_item_result);
result_array[i] = array_item_result;
}
*value = result_array;
} else if (this->ResultIsObject()) {
Json::Value result_object;
std::wstring property_name_list(L"");
status_code = this->GetPropertyNameList(&property_name_list);
std::vector<std::wstring> property_names;
size_t end_position(0);
size_t start_position(0);
while (true) {
std::wstring property_name(L"");
end_position = property_name_list.find_first_of(L",", start_position);
if(end_position == std::wstring::npos) {
property_names.push_back(property_name_list.substr(start_position, property_name_list.size() - start_position));
break;
} else {
property_names.push_back(property_name_list.substr(start_position, end_position - start_position));
start_position = end_position + 1;
}
}
for (size_t i = 0; i < property_names.size(); ++i) {
Json::Value property_value_result;
int property_value_status = this->GetPropertyValue(manager, property_names[i], &property_value_result);
std::string name(CW2A(property_names[i].c_str(), CP_UTF8));
result_object[name] = property_value_result;
}
*value = result_object;
} else {
IHTMLElement *node = (IHTMLElement*) this->result_.pdispVal;
std::tr1::shared_ptr<ElementWrapper> element_wrapper;
manager->AddManagedElement(node, &element_wrapper);
*value = element_wrapper->ConvertToJson();
}
} else {
status_code = EUNKNOWNSCRIPTRESULT;
}
return status_code;
}
std::wstring ScriptWrapper::GetResultObjectTypeName() {
std::wstring name(L"");
if (this->result_.vt == VT_DISPATCH) {
CComPtr<ITypeInfo> typeinfo;
HRESULT get_type_info_result = this->result_.pdispVal->GetTypeInfo(0, LOCALE_USER_DEFAULT, &typeinfo);
TYPEATTR* type_attr;
CComBSTR name_bstr;
if (SUCCEEDED(get_type_info_result) && SUCCEEDED(typeinfo->GetTypeAttr(&type_attr))
&& SUCCEEDED(typeinfo->GetDocumentation(-1, &name_bstr, 0, 0, 0))) {
typeinfo->ReleaseTypeAttr(type_attr);
name = name_bstr.Copy();
}
}
return name;
}
int ScriptWrapper::GetPropertyNameList(std::wstring *property_names) {
// Loop through the properties, appending the name of each one to the string.
std::wstring get_names_script(L"(function(){return function() { var name_list = ''; for (var name in arguments[0]) { if (name_list.length > 0) name_list+= ','; name_list += name } return name_list;}})();");
ScriptWrapper get_names_script_wrapper(this->script_engine_host_, get_names_script, 1);
get_names_script_wrapper.AddArgument(this->result_);
int get_names_result = get_names_script_wrapper.Execute();
if (get_names_result != SUCCESS) {
return get_names_result;
}
// Expect the return type to be an integer. A non-integer means this was
// not an array after all.
if (!get_names_script_wrapper.ResultIsString()) {
return EUNEXPECTEDJSERROR;
}
*property_names = get_names_script_wrapper.result().bstrVal;
return SUCCESS;
}
int ScriptWrapper::GetPropertyValue(BrowserManager *manager, const std::wstring& property_name, Json::Value *property_value){
std::wstring get_value_script(L"(function(){return function() {return arguments[0][arguments[1]];}})();");
ScriptWrapper get_value_script_wrapper(this->script_engine_host_, get_value_script, 2);
get_value_script_wrapper.AddArgument(this->result_);
get_value_script_wrapper.AddArgument(property_name);
int get_value_result = get_value_script_wrapper.Execute();
if (get_value_result != SUCCESS) {
return get_value_result;
}
int property_value_status = get_value_script_wrapper.ConvertResultToJsonValue(manager, property_value);
return SUCCESS;
}
int ScriptWrapper::GetArrayLength(long *length) {
// Prepare an array for the Javascript execution, containing only one
// element - the original returned array from a JS execution.
std::wstring get_length_script(L"(function(){return function() {return arguments[0].length;}})();");
ScriptWrapper get_length_script_wrapper(this->script_engine_host_, get_length_script, 1);
get_length_script_wrapper.AddArgument(this->result_);
int length_result = get_length_script_wrapper.Execute();
if (length_result != SUCCESS) {
return length_result;
}
// Expect the return type to be an integer. A non-integer means this was
// not an array after all.
if (!get_length_script_wrapper.ResultIsInteger()) {
return EUNEXPECTEDJSERROR;
}
*length = get_length_script_wrapper.result().lVal;
return SUCCESS;
}
int ScriptWrapper::GetArrayItem(BrowserManager *manager, long index, Json::Value *item){
std::wstring get_array_item_script(L"(function(){return function() {return arguments[0][arguments[1]];}})();");
ScriptWrapper get_array_item_script_wrapper(this->script_engine_host_, get_array_item_script, 2);
get_array_item_script_wrapper.AddArgument(this->result_);
get_array_item_script_wrapper.AddArgument(index);
int get_item_result = get_array_item_script_wrapper.Execute();
if (get_item_result != SUCCESS) {
return get_item_result;
}
int array_item_status = get_array_item_script_wrapper.ConvertResultToJsonValue(manager, item);
return SUCCESS;
}
bool ScriptWrapper::CreateAnonymousFunction(VARIANT *result) {
CComBSTR function_eval_script(L"window.document.__webdriver_script_fn = ");
function_eval_script.Append(this->script_.c_str());
CComBSTR code(function_eval_script);
CComBSTR lang(L"JScript");
CComVariant exec_script_result;
CComPtr<IHTMLWindow2> window;
HRESULT hr = this->script_engine_host_->get_parentWindow(&window);
if (FAILED(hr)) {
return false;
}
hr = window->execScript(code, lang, &exec_script_result);
if (FAILED(hr)) {
return false;
}
OLECHAR FAR* function_object_name(L"__webdriver_script_fn");
DISPID dispid_function_object;
hr = this->script_engine_host_->GetIDsOfNames(IID_NULL, &function_object_name, 1, LOCALE_USER_DEFAULT, &dispid_function_object);
if (FAILED(hr)) {
return false;
}
// get the value of eval result
DISPPARAMS no_args_dispatch_parameters = { NULL, NULL, 0, 0 };
hr = this->script_engine_host_->Invoke(dispid_function_object, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &no_args_dispatch_parameters, result, NULL, NULL);
if (FAILED(hr)) {
return false;
}
return true;
}
} // namespace webdriver