Bläddra i källkod

javascript eval

David Rose 16 år sedan
förälder
incheckning
aa171dde99

+ 1 - 1
direct/src/plugin/FileSpec.py

@@ -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)

+ 66 - 8
direct/src/plugin/p3dInstance.cxx

@@ -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;
   }

+ 23 - 0
direct/src/plugin/p3dObject.cxx

@@ -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

+ 1 - 0
direct/src/plugin/p3dObject.h

@@ -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;
 

+ 5 - 2
direct/src/plugin/p3dPythonRun.cxx

@@ -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");

+ 5 - 0
direct/src/plugin/p3dSession.cxx

@@ -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).

+ 14 - 1
direct/src/plugin/p3d_plugin.h

@@ -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;
 

+ 43 - 1
direct/src/plugin_npapi/ppBrowserObject.cxx

@@ -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;

+ 1 - 0
direct/src/plugin_npapi/ppBrowserObject.h

@@ -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();

+ 13 - 21
direct/src/plugin_npapi/ppInstance.cxx

@@ -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;
   }
 }
 

+ 2 - 2
direct/src/plugin_npapi/ppInstance.h

@@ -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);

+ 87 - 14
direct/src/showutil/runp3d.py

@@ -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)