javascript eval

This commit is contained in:
David Rose 2009-07-08 21:41:09 +00:00
parent b4f51d5ca6
commit aa171dde99
12 changed files with 260 additions and 49 deletions

View File

@ -17,5 +17,5 @@ class FileSpec:
self.hash = hv.asHex()
def getParams(self):
return 'filename="%s" size=%s timestamp=%s hash="%s"' % (
return 'filename="%s" size="%s" timestamp="%s" hash="%s"' % (
self.filename, self.size, self.timestamp, self.hash)

View File

@ -475,7 +475,7 @@ handle_notify_request(P3D_request *request) {
const char *message = request->_request._notify._message;
if (strcmp(message, "onwindowopen") == 0) {
// The process told us that it just succesfully opened its
// window.
// window. Tear down the splash window.
_instance_window_opened = true;
if (_splash_window != NULL) {
delete _splash_window;
@ -495,6 +495,7 @@ handle_script_request(P3D_request *request) {
assert(request->_request_type == P3D_RT_script);
P3D_object *object = request->_request._script._object;
bool needs_response = request->_request._script._needs_response;
int unique_id = request->_request._script._unique_id;
switch (request->_request._script._op) {
case P3D_SO_get_property:
@ -515,8 +516,12 @@ handle_script_request(P3D_request *request) {
xcommand->LinkEndChild(_session->p3dobj_to_xml(result));
P3D_OBJECT_FINISH(result);
}
_session->send_command(doc);
if (needs_response) {
_session->send_command(doc);
} else {
delete doc;
}
}
break;
@ -546,7 +551,11 @@ handle_script_request(P3D_request *request) {
xvalue->SetAttribute("value", (int)result);
xcommand->LinkEndChild(xvalue);
_session->send_command(doc);
if (needs_response) {
_session->send_command(doc);
} else {
delete doc;
}
}
break;
@ -570,7 +579,11 @@ handle_script_request(P3D_request *request) {
xvalue->SetAttribute("value", (int)result);
xcommand->LinkEndChild(xvalue);
_session->send_command(doc);
if (needs_response) {
_session->send_command(doc);
} else {
delete doc;
}
}
break;
@ -581,8 +594,8 @@ handle_script_request(P3D_request *request) {
request->_request._script._values,
request->_request._script._num_values);
// Reset the value count to 0 so we won't double-delete the
// values when the request is deleted (the above call has
// already deleted them).
// parameter values when the request is deleted (the above call
// has already deleted them).
request->_request._script._num_values = 0;
// Feed the result back down to the subprocess.
@ -600,7 +613,52 @@ handle_script_request(P3D_request *request) {
P3D_OBJECT_FINISH(result);
}
_session->send_command(doc);
if (needs_response) {
_session->send_command(doc);
} else {
delete doc;
}
}
break;
case P3D_SO_eval:
{
P3D_object *result;
if (request->_request._script._num_values == 1) {
P3D_object *expression = request->_request._script._values[0];
int size = P3D_OBJECT_GET_STRING(expression, NULL, 0);
char *buffer = new char[size + 1];
P3D_OBJECT_GET_STRING(expression, buffer, size + 1);
result = P3D_OBJECT_EVAL(object, buffer);
logfile << " eval " << *object << ": " << buffer << ", result = " << result << "\n";
delete[] buffer;
} else {
// Wrong number of values. Error.
result = NULL;
}
// Feed the result back down to the subprocess.
TiXmlDocument *doc = new TiXmlDocument;
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", "");
TiXmlElement *xcommand = new TiXmlElement("command");
xcommand->SetAttribute("cmd", "script_response");
xcommand->SetAttribute("unique_id", unique_id);
doc->LinkEndChild(decl);
doc->LinkEndChild(xcommand);
if (result != NULL) {
xcommand->LinkEndChild(_session->p3dobj_to_xml(result));
P3D_OBJECT_FINISH(result);
}
logfile << "eval response: " << *doc << "\n";
if (needs_response) {
_session->send_command(doc);
} else {
delete doc;
}
}
break;
}

View File

@ -79,6 +79,11 @@ object_call(const P3D_object *object, const char *method_name,
return ((const P3DObject *)object)->call(method_name, params, num_params);
}
static P3D_object *
object_eval(const P3D_object *object, const char *expression) {
return ((const P3DObject *)object)->eval(expression);
}
P3D_class_definition P3DObject::_object_class = {
&object_finish,
&object_copy,
@ -91,6 +96,7 @@ P3D_class_definition P3DObject::_object_class = {
&object_get_property,
&object_set_property,
&object_call,
&object_eval,
};
// The next functions are used to construct the generic
@ -166,6 +172,11 @@ generic_call(const P3D_object *object, const char *method_name,
return NULL;
}
static P3D_object *
generic_eval(const P3D_object *object, const char *expression) {
return NULL;
}
P3D_class_definition P3DObject::_generic_class = {
&generic_finish,
&generic_copy,
@ -178,6 +189,7 @@ P3D_class_definition P3DObject::_generic_class = {
&generic_get_property,
&generic_set_property,
&generic_call,
&generic_eval,
};
////////////////////////////////////////////////////////////////////
@ -301,6 +313,17 @@ call(const string &method_name, P3D_object *params[], int num_params) const {
return NULL;
}
////////////////////////////////////////////////////////////////////
// Function: P3DObject::eval
// Access: Public, Virtual
// Description: Evaluates an arbitrary JavaScript expression. None
// of the P3DObject classes implement this.
////////////////////////////////////////////////////////////////////
P3D_object *P3DObject::
eval(const string &expression) const {
return NULL;
}
////////////////////////////////////////////////////////////////////
// Function: P3DObject::output
// Access: Public, Virtual

View File

@ -48,6 +48,7 @@ public:
virtual P3D_object *call(const string &method_name,
P3D_object *params[], int num_params) const;
virtual P3D_object *eval(const string &expression) const;
virtual void output(ostream &out) const;

View File

@ -598,14 +598,17 @@ py_request_func(PyObject *args) {
PyObject *object;
const char *property_name;
PyObject *value;
int needs_response;
int unique_id;
if (!PyArg_ParseTuple(extra_args, "sOsOi",
&operation, &object, &property_name, &value, &unique_id)) {
if (!PyArg_ParseTuple(extra_args, "sOsOii",
&operation, &object, &property_name, &value,
&needs_response, &unique_id)) {
return NULL;
}
xrequest->SetAttribute("operation", operation);
xrequest->SetAttribute("property_name", property_name);
xrequest->SetAttribute("needs_response", (int)(needs_response != 0));
xrequest->SetAttribute("unique_id", unique_id);
TiXmlElement *xobject = pyobj_to_xml(object);
xobject->SetValue("object");

View File

@ -684,6 +684,8 @@ rt_make_p3d_request(TiXmlElement *xrequest) {
const char *operation = xrequest->Attribute("operation");
TiXmlElement *xobject = xrequest->FirstChildElement("object");
const char *property_name = xrequest->Attribute("property_name");
int needs_response = 0;
xrequest->Attribute("needs_response", &needs_response);
int unique_id = 0;
xrequest->Attribute("unique_id", &unique_id);
@ -697,6 +699,8 @@ rt_make_p3d_request(TiXmlElement *xrequest) {
op = P3D_SO_del_property;
} else if (strcmp(operation, "call") == 0) {
op = P3D_SO_call;
} else if (strcmp(operation, "eval") == 0) {
op = P3D_SO_eval;
} else {
// An unexpected operation.
return NULL;
@ -710,6 +714,7 @@ rt_make_p3d_request(TiXmlElement *xrequest) {
if (property_name != NULL) {
request->_request._script._property_name = strdup(property_name);
}
request->_request._script._needs_response = (needs_response != 0);
request->_request._script._unique_id = unique_id;
// Fill in the value(s).

View File

@ -74,7 +74,7 @@ extern "C" {
libraries match. It should be passed to P3D_initialize() (below).
This number will be incremented whenever there are changes to any of
the interface specifications defined in this header file. */
#define P3D_API_VERSION 3
#define P3D_API_VERSION 4
/************************ GLOBAL FUNCTIONS **************************/
@ -391,6 +391,15 @@ typedef P3D_object *
P3D_object_call_method(const P3D_object *object, const char *method_name,
P3D_object *params[], int num_params);
/* Evaluates an arbitrary JavaScript expression in the context of the
object.
The return value is a newly-allocated P3D_object on success, or
NULL on failure. Ownership of the return value is transferred to
the caller. */
typedef P3D_object *
P3D_object_eval_method(const P3D_object *object, const char *expression);
/* This defines the class structure that implements all of the above
methods. */
typedef struct _P3D_class_definition {
@ -408,6 +417,7 @@ typedef struct _P3D_class_definition {
P3D_object_set_property_method *_set_property;
P3D_object_call_method *_call;
P3D_object_eval_method *_eval;
} P3D_class_definition;
@ -435,6 +445,7 @@ struct _P3D_object {
#define P3D_OBJECT_SET_PROPERTY(object, property, value) ((object)->_class->_set_property((object), (property), (value)))
#define P3D_OBJECT_CALL(object, method_name, params, num_params) ((object)->_class->_call((object), (method_name), (params), (num_params)))
#define P3D_OBJECT_EVAL(object, expression) ((object)->_class->_eval((object), (expression)))
/* The following function types are once again meant to define
@ -604,6 +615,7 @@ typedef enum {
P3D_SO_set_property,
P3D_SO_del_property,
P3D_SO_call,
P3D_SO_eval,
} P3D_script_operation;
typedef struct {
P3D_object *_object;
@ -611,6 +623,7 @@ typedef struct {
const char *_property_name;
P3D_object **_values;
int _num_values;
bool _needs_response;
int _unique_id;
} P3D_request_script;

View File

@ -56,6 +56,11 @@ object_call(const P3D_object *object, const char *method_name,
return ((const PPBrowserObject *)object)->call(method_name, params, num_params);
}
static P3D_object *
object_eval(const P3D_object *object, const char *expression) {
return ((const PPBrowserObject *)object)->eval(expression);
}
P3D_class_definition *PPBrowserObject::_browser_object_class;
////////////////////////////////////////////////////////////////////
@ -184,11 +189,15 @@ set_property(const string &property, P3D_object *value) {
////////////////////////////////////////////////////////////////////
P3D_object *PPBrowserObject::
call(const string &method_name, P3D_object *params[], int num_params) const {
logfile << "call " << method_name << "(";
// First, convert all of the parameters.
NPVariant *npparams = new NPVariant[num_params];
for (int i = 0; i < num_params; ++i) {
_instance->p3dobj_to_variant(&npparams[i], params[i]);
_instance->output_np_variant(logfile, npparams[i]);
logfile << ", ";
}
logfile << ")\n";
NPVariant result;
if (method_name.empty()) {
@ -196,7 +205,7 @@ call(const string &method_name, P3D_object *params[], int num_params) const {
if (!browser->invokeDefault(_instance->get_npp_instance(), _npobj,
npparams, num_params, &result)) {
// Failed to invoke.
logfile << "invoke failed\n" << flush;
logfile << "invokeDefault failed\n" << flush;
delete[] npparams;
return NULL;
}
@ -207,11 +216,14 @@ call(const string &method_name, P3D_object *params[], int num_params) const {
if (!browser->invoke(_instance->get_npp_instance(), _npobj, method_id,
npparams, num_params, &result)) {
// Failed to invoke.
logfile << "invoke failed\n" << flush;
delete[] npparams;
return NULL;
}
}
delete[] npparams;
logfile << "invoke succeeded\n" << flush;
P3D_object *object = _instance->variant_to_p3dobj(&result);
@ -219,6 +231,35 @@ call(const string &method_name, P3D_object *params[], int num_params) const {
return object;
}
////////////////////////////////////////////////////////////////////
// Function: PPBrowserObject::eval
// Access: Public
// Description: Evaluates the indicated JavaScript expression in the
// context of the object.
////////////////////////////////////////////////////////////////////
P3D_object *PPBrowserObject::
eval(const string &expression) const {
logfile << "eval " << expression << "\n";
NPString npexpr;
npexpr.utf8characters = expression.c_str();
npexpr.utf8length = expression.length();
NPVariant result;
if (!browser->evaluate(_instance->get_npp_instance(), _npobj,
&npexpr, &result)) {
// Failed to eval.
logfile << "eval failed\n" << flush;
return NULL;
}
logfile << "eval succeeded\n" << flush;
P3D_object *object = _instance->variant_to_p3dobj(&result);
browser->releasevariantvalue(&result);
return object;
}
////////////////////////////////////////////////////////////////////
// Function: PPBrowserObject::get_class_definition
// Access: Private, Static
@ -239,6 +280,7 @@ get_class_definition() {
_browser_object_class->_get_property = &object_get_property;
_browser_object_class->_set_property = &object_set_property;
_browser_object_class->_call = &object_call;
_browser_object_class->_eval = &object_eval;
}
return _browser_object_class;

View File

@ -41,6 +41,7 @@ public:
P3D_object *call(const string &method_name,
P3D_object *params[], int num_params) const;
P3D_object *eval(const string &expression) const;
private:
static P3D_class_definition *get_class_definition();

View File

@ -74,13 +74,9 @@ PPInstance::
_p3d_inst = NULL;
}
// It's not clear why we shoudn't release this object now, but if we
// do we crash (at least on Windows).
/*
if (_script_object != NULL) {
browser->releaseobject(_script_object);
}
*/
// Free the tokens we allocated.
Tokens::iterator ti;
@ -430,14 +426,11 @@ handle_request(P3D_request *request) {
// with the proper P3D object pointer.
P3D_object *obj = P3D_instance_get_panda_script_object(_p3d_inst);
_script_object->set_p3d_object(obj);
logfile << "got onpythonload\n";
}
}
break;
case P3D_RT_script:
// We're allowed to ignore this.
break;
default:
// Some request types are not handled.
logfile << "Unhandled request: " << request->_request_type << "\n";
@ -481,6 +474,8 @@ handle_request_loop() {
NPObject *PPInstance::
get_panda_script_object() {
if (_script_object != NULL) {
// NPRuntime "steals" a reference to this object.
browser->retainobject(_script_object);
return _script_object;
}
@ -492,10 +487,7 @@ get_panda_script_object() {
_script_object = PPPandaObject::make_new(this, obj);
// It's not clear why we need to explicitly retain this object now,
// but if we don't we crash.
browser->retainobject(_script_object);
return _script_object;
}
@ -946,28 +938,28 @@ send_window() {
}
////////////////////////////////////////////////////////////////////
// Function: PPInstance::show_np_variant
// Access: Private
// Function: PPInstance::output_np_variant
// Access: Public
// Description: Outputs the variant value.
////////////////////////////////////////////////////////////////////
void PPInstance::
show_np_variant(const NPVariant &result) {
output_np_variant(ostream &out, const NPVariant &result) {
if (NPVARIANT_IS_NULL(result)) {
logfile << "null";
out << "null";
} else if (NPVARIANT_IS_VOID(result)) {
logfile << "void";
out << "void";
} else if (NPVARIANT_IS_BOOLEAN(result)) {
logfile << "bool " << NPVARIANT_TO_BOOLEAN(result);
out << "bool " << NPVARIANT_TO_BOOLEAN(result);
} else if (NPVARIANT_IS_INT32(result)) {
logfile << "int " << NPVARIANT_TO_INT32(result);
out << "int " << NPVARIANT_TO_INT32(result);
} else if (NPVARIANT_IS_DOUBLE(result)) {
logfile << "double " << NPVARIANT_TO_DOUBLE(result);
out << "double " << NPVARIANT_TO_DOUBLE(result);
} else if (NPVARIANT_IS_STRING(result)) {
NPString str = NPVARIANT_TO_STRING(result);
logfile << "string " << string(str.utf8characters, str.utf8length);
out << "string " << string(str.utf8characters, str.utf8length);
} else if (NPVARIANT_IS_OBJECT(result)) {
NPObject *child = NPVARIANT_TO_OBJECT(result);
logfile << "object " << child;
out << "object " << child;
}
}

View File

@ -58,6 +58,8 @@ public:
void p3dobj_to_variant(NPVariant *result, const P3D_object *object);
P3D_object *variant_to_p3dobj(const NPVariant *variant);
static void output_np_variant(ostream &out, const NPVariant &result);
private:
void start_download(const string &url, PPDownloadRequest *req);
void downloaded_file(PPDownloadRequest *req, const string &filename);
@ -72,8 +74,6 @@ private:
void create_instance();
void send_window();
void show_np_variant(const NPVariant &result);
#ifdef _WIN32
static LONG
window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);

View File

@ -56,6 +56,7 @@ class AppRunner(DirectObject):
self.gotWindow = False
self.gotP3DFilename = False
self.started = False
self.windowOpened = False
self.windowPrc = None
# Store this Null instance where the application can easily
@ -76,6 +77,10 @@ class AppRunner(DirectObject):
# e.g. self.dom.document will be the document.
self.dom = None
# This is the list of expressions we will evaluate when
# self.dom gets assigned.
self.deferredEvals = []
# This is the default requestFunc that is installed if we
# never call setRequestFunc().
def defaultRequestFunc(*args):
@ -175,18 +180,27 @@ class AppRunner(DirectObject):
self.dom = dom
print "setBrowserScriptObject(%s)" % (dom)
# Now evaluate any deferred expressions.
for expression in self.deferredEvals:
self.scriptRequest('eval', self.dom, value = expression,
needsResponse = False)
self.deferredEvals = []
def setP3DFilename(self, p3dFilename, tokens = [],
instanceId = None):
# One day we will have support for multiple instances within a
# Python session. Against that day, we save the instance ID
# for this instance.
self.instanceId = instanceId
# Now that we have an instanceId, we can respond to queries
# and such.
self.sendRequest('notify', 'onpythonload')
tokenDict = dict(tokens)
self.tokens = tokens
self.tokenDict = dict(tokens)
# Tell the browser that Python is up and running, and ready to
# respond to queries.
self.notifyRequest('onpythonload')
# Now go load the applet.
fname = Filename.fromOsSpecific(p3dFilename)
if not p3dFilename:
# If we didn't get a literal filename, we have to download it
@ -194,7 +208,7 @@ class AppRunner(DirectObject):
fname = Filename.temporary('', 'p3d_')
fname.setExtension('p3d')
p3dFilename = fname.toOsSpecific()
src = tokenDict.get('src', None)
src = self.tokenDict.get('src', None)
if not src:
raise ArgumentError, "No Panda app specified."
@ -241,6 +255,7 @@ class AppRunner(DirectObject):
loadPrcFileData(pathname, data)
self.gotP3DFilename = True
self.startIfReady()
def setupWindow(self, windowType, x, y, width, height, parent):
@ -291,14 +306,53 @@ class AppRunner(DirectObject):
self.requestFunc = func
def sendRequest(self, request, *args):
""" Delivers a request to the browser via self.requestFunc.
This low-level function is not intended to be called directly
by user code. """
assert self.requestFunc
return self.requestFunc(self.instanceId, request, args)
def windowEvent(self, win):
self.sendRequest('notify', 'onwindowopen')
""" This method is called when we get a window event. We
listen for this to detect when the window has been
successfully opened. """
if not self.windowOpened:
self.notifyRequest('onwindowopen')
self.windowOpened = True
def notifyRequest(self, message):
""" Delivers a notify request to the browser. This is a "this
happened" type notification; it optionally triggers some
JavaScript code execution, and may also trigger some internal
automatic actions. (For instance, the plugin takes down the
splash window when it sees the onwindowopen notification. """
self.sendRequest('notify', message)
# Now process any JavaScript that might be waiting for the
# event as well. These are the JavaScript expressions that
# were specified in the HTML embed or object tag.
#expression = self.tokenDict.get(message)
#if expression:
# self.evalScript(expression)
def evalScript(self, expression):
""" Evaluates an arbitrary JavaScript expression in the global
DOM space. This may be deferred if necessary if self.dom has
not yet been assigned. """
if not self.dom:
# Defer the expression.
self.deferredEvals.append(expression)
else:
# Evaluate it now.
self.scriptRequest('eval', self.dom, value = expression,
needsResponse = False)
def scriptRequest(self, operation, object, propertyName = '',
value = None):
value = None, needsResponse = True):
""" Issues a new script request to the browser. This queries
or modifies one of the browser's DOM properties. This method
blocks until the return value is received from the browser,
@ -320,11 +374,12 @@ class AppRunner(DirectObject):
uniqueId = self.nextScriptId
self.nextScriptId += 1
self.sendRequest('script', operation, object,
propertyName, value, uniqueId);
propertyName, value, needsResponse, uniqueId);
# Now wait for the response to come in.
result = self.sendRequest('wait_script_response', uniqueId)
return result
if needsResponse:
# Now wait for the response to come in.
result = self.sendRequest('wait_script_response', uniqueId)
return result
def parseSysArgs(self):
""" Converts sys.argv into (p3dFilename, tokens). """
@ -397,7 +452,25 @@ class BrowserObject:
parentObj, attribName = self.__boundMethod
if parentObj:
# Call it as a method.
result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args)
needsResponse = True
if parentObj is self.__runner.dom and attribName == 'alert':
# As a special hack, we don't wait for the return
# value from the alert() call, since this is a
# blocking call, and waiting for this could cause
# problems.
needsResponse = False
if parentObj is self.__runner.dom and attribName == 'eval' and len(args) == 1 and isinstance(args[0], types.StringTypes):
# As another special hack, we make dom.eval() a
# special case, and map it directly into an eval()
# call. If the string begins with 'void ', we further
# assume we're not waiting for a response.
if args[0].startswith('void '):
needsResponse = False
result = self.__runner.scriptRequest('eval', parentObj, value = args[0], needsResponse = needsResponse)
else:
# This is a normal method call.
result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
else:
# Call it as a plain function.
result = self.__runner.scriptRequest('call', self, value = args)