Browse Source

helper file

David Rose 16 years ago
parent
commit
f66e45e13a
1 changed files with 251 additions and 0 deletions
  1. 251 0
      direct/src/showutil/JavaScript.py

+ 251 - 0
direct/src/showutil/JavaScript.py

@@ -0,0 +1,251 @@
+""" This module defines some simple classes and instances which are
+useful when writing code that integrates with JavaScript, especially
+code that runs in a browser via the web plugin. """
+
+
+class UndefinedObject:
+    """ This is a special object that is returned by the browser to
+    represent an "undefined" value, typically the value for an
+    uninitialize variable or undefined property.  It has no
+    attributes, similar to None, but it is a slightly different
+    concept in JavaScript. """
+
+    def __nonzero__(self):
+        return False
+
+    def __str__(self):
+        return "Undefined"
+
+# In fact, we normally always return this precise instance of the
+# UndefinedObject.
+Undefined = UndefinedObject()
+
+class ConcreteStruct:
+    """ Python objects that inherit from this class are passed to
+    JavaScript as a concrete struct: a mapping from string -> value,
+    with no methods, passed by value.  This can be more optimal than
+    traditional Python objects which are passed by reference,
+    especially for small objects which might be repeatedly referenced
+    on the JavaScript side. """
+
+    def __init__(self):
+        pass
+
+    def getConcreteProperties(self):
+        """ Returns a list of 2-tuples of the (key, value) pairs
+        that are to be passed to the concrete instance.  By default,
+        this returns all properties of the object. """
+        return self.__dict__.items()
+
+class BrowserObject:
+    """ This class provides the Python wrapper around some object that
+    actually exists in the plugin host's namespace, e.g. a JavaScript
+    or DOM object. """
+
+    def __init__(self, runner, objectId):
+        self.__dict__['_BrowserObject__runner'] = runner
+        self.__dict__['_BrowserObject__objectId'] = objectId
+
+        # This element is filled in by __getattr__; it connects
+        # the object to its parent.
+        self.__dict__['_BrowserObject__boundMethod'] = (None, None)
+
+    def __del__(self):
+        # When the BrowserObject destructs, tell the parent process it
+        # doesn't need to keep around its corresponding P3D_object any
+        # more.
+        self.__runner.dropObject(self.__objectId)
+
+    def __str__(self):
+        parentObj, attribName = self.__boundMethod
+        if parentObj:
+            # Format it from its parent.
+            return "%s.%s" % (parentObj, attribName)
+        else:
+            # Format it directly.
+            return "BrowserObject(%s)" % (self.__objectId)
+
+    def __nonzero__(self):
+        return True
+
+    def __call__(self, *args):
+        try:
+            parentObj, attribName = self.__boundMethod
+            if parentObj:
+                # Call it as a method.
+                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.
+                    try:
+                        result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
+                    except EnvironmentError:
+                        # Problem on the call.  Maybe no such method?
+                        raise AttributeError
+            else:
+                # Call it as a plain function.
+                result = self.__runner.scriptRequest('call', self, value = args)
+        except EnvironmentError:
+            # Some odd problem on the call.
+            raise TypeError
+
+        return result
+
+    def __getattr__(self, name):
+        """ Remaps attempts to query an attribute, as in obj.attr,
+        into the appropriate calls to query the actual browser object
+        under the hood.  """
+
+        try:
+            value = self.__runner.scriptRequest('get_property', self,
+                                                propertyName = name)
+        except EnvironmentError:
+            # Failed to retrieve the attribute.  But maybe there's a
+            # method instead?
+            if self.__runner.scriptRequest('has_method', self, propertyName = name):
+                # Yes, so create a method wrapper for it.
+                return MethodWrapper(self.__runner, self, name)
+            
+            raise AttributeError(name)
+
+        if isinstance(value, BrowserObject):
+            # Fill in the parent object association, so __call__ can
+            # properly call a method.  (Javascript needs to know the
+            # method container at the time of the call, and doesn't
+            # store it on the function object.)
+            value.__dict__['_BrowserObject__boundMethod'] = (self, name)
+
+        return value
+
+    def __setattr__(self, name, value):
+        if name in self.__dict__:
+            self.__dict__[name] = value
+            return
+
+        result = self.__runner.scriptRequest('set_property', self,
+                                             propertyName = name,
+                                             value = value)
+        if not result:
+            raise AttributeError(name)
+
+    def __delattr__(self, name):
+        if name in self.__dict__:
+            del self.__dict__[name]
+            return
+
+        result = self.__runner.scriptRequest('del_property', self,
+                                             propertyName = name)
+        if not result:
+            raise AttributeError(name)
+
+    def __getitem__(self, key):
+        """ Remaps attempts to query an attribute, as in obj['attr'],
+        into the appropriate calls to query the actual browser object
+        under the hood.  Following the JavaScript convention, we treat
+        obj['attr'] almost the same as obj.attr. """
+
+        try:
+            value = self.__runner.scriptRequest('get_property', self,
+                                                propertyName = str(key))
+        except EnvironmentError:
+            # Failed to retrieve the property.  We return IndexError
+            # for numeric keys so we can properly support Python's
+            # iterators, but we return KeyError for string keys to
+            # emulate mapping objects.
+            if isinstance(key, types.StringTypes):
+                raise KeyError(key)
+            else:
+                raise IndexError(key)
+
+        return value
+
+    def __setitem__(self, key, value):
+        result = self.__runner.scriptRequest('set_property', self,
+                                             propertyName = str(key),
+                                             value = value)
+        if not result:
+            if isinstance(key, types.StringTypes):
+                raise KeyError(key)
+            else:
+                raise IndexError(key)
+
+    def __delitem__(self, key):
+        result = self.__runner.scriptRequest('del_property', self,
+                                             propertyName = str(key))
+        if not result:
+            if isinstance(key, types.StringTypes):
+                raise KeyError(key)
+            else:
+                raise IndexError(key)
+
+class MethodWrapper:
+    """ This is a Python wrapper around a property of a BrowserObject
+    that doesn't appear to be a first-class object in the Python
+    sense, but is nonetheless a callable method. """
+
+    def __init__(self, runner, parentObj, objectId):
+        self.__dict__['_MethodWrapper__runner'] = runner
+        self.__dict__['_MethodWraper__boundMethod'] = (parentObj, objectId)
+
+    def __str__(self):
+        parentObj, attribName = self.__boundMethod
+        return "%s.%s" % (parentObj, attribName)
+
+    def __nonzero__(self):
+        return True
+
+    def __call__(self, *args):
+        try:
+            parentObj, attribName = self.__boundMethod
+            # Call it as a method.
+            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.
+                try:
+                    result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
+                except EnvironmentError:
+                    # Problem on the call.  Maybe no such method?
+                    raise AttributeError
+
+        except EnvironmentError:
+            # Some odd problem on the call.
+            raise TypeError
+
+        return result
+
+    def __setattr__(self, name, value):
+        """ setattr will generally fail on method objects. """
+        raise AttributeError(name)
+
+    def __delattr__(self, name):
+        """ delattr will generally fail on method objects. """
+        raise AttributeError(name)