helper file

This commit is contained in:
David Rose 2009-07-14 22:11:46 +00:00
parent 6794656499
commit f66e45e13a
1 changed files with 251 additions and 0 deletions

View File

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