mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-25 12:42:41 -06:00
293 lines
12 KiB
Python
293 lines
12 KiB
Python
|
""" 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. """
|
||
|
|
||
|
import types
|
||
|
|
||
|
class UndefinedObject:
|
||
|
""" This is a special object that is returned by the browser to
|
||
|
represent an "undefined" or "void" value, typically the value for
|
||
|
an uninitialized 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. You can override this
|
||
|
to restrict the set of properties that are uploaded. """
|
||
|
|
||
|
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__childObject'] = (None, None)
|
||
|
|
||
|
# This is a cache of method names to MethodWrapper objects in
|
||
|
# the parent object.
|
||
|
self.__dict__['_BrowserObject__methods'] = {}
|
||
|
|
||
|
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 __cacheMethod(self, methodName):
|
||
|
""" Stores a pointer to the named method on this object, so
|
||
|
that the next time __getattr__ is called, it can retrieve the
|
||
|
method wrapper without having to query the browser. This
|
||
|
cache assumes that callable methods don't generally come and
|
||
|
go on and object.
|
||
|
|
||
|
The return value is the MethodWrapper object. """
|
||
|
|
||
|
method = self.__methods.get(methodName, None)
|
||
|
if method is None:
|
||
|
method = MethodWrapper(self.__runner, self, methodName)
|
||
|
self.__methods[methodName] = method
|
||
|
return method
|
||
|
|
||
|
def __str__(self):
|
||
|
return self.toString()
|
||
|
|
||
|
def __nonzero__(self):
|
||
|
return True
|
||
|
|
||
|
def __call__(self, *args, **kw):
|
||
|
needsResponse = True
|
||
|
if 'needsResponse' in kw:
|
||
|
needsResponse = kw['needsResponse']
|
||
|
del kw['needsResponse']
|
||
|
if kw:
|
||
|
raise ArgumentError, 'Keyword arguments not supported'
|
||
|
|
||
|
try:
|
||
|
parentObj, attribName = self.__childObject
|
||
|
if parentObj:
|
||
|
# Call it as a method.
|
||
|
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
|
||
|
|
||
|
# Hey, the method call appears to have succeeded.
|
||
|
# Cache the method object on the parent so we won't
|
||
|
# have to look up the method wrapper again next time.
|
||
|
parentObj.__cacheMethod(attribName)
|
||
|
|
||
|
else:
|
||
|
# Call it as a plain function.
|
||
|
result = self.__runner.scriptRequest('call', self, value = args, needsResponse = needsResponse)
|
||
|
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. """
|
||
|
|
||
|
# First check to see if there's a cached method wrapper from a
|
||
|
# previous call.
|
||
|
method = self.__methods.get(name, None)
|
||
|
if method:
|
||
|
return method
|
||
|
|
||
|
# No cache. Go query the browser for the desired value.
|
||
|
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 self.__cacheMethod(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__childObject'] = (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__['_MethodWrapper__childObject'] = (parentObj, objectId)
|
||
|
|
||
|
def __str__(self):
|
||
|
parentObj, attribName = self.__childObject
|
||
|
return "%s.%s" % (parentObj, attribName)
|
||
|
|
||
|
def __nonzero__(self):
|
||
|
return True
|
||
|
|
||
|
def __call__(self, *args, **kw):
|
||
|
needsResponse = True
|
||
|
if 'needsResponse' in kw:
|
||
|
needsResponse = kw['needsResponse']
|
||
|
del kw['needsResponse']
|
||
|
if kw:
|
||
|
raise ArgumentError, 'Keyword arguments not supported'
|
||
|
|
||
|
try:
|
||
|
parentObj, attribName = self.__childObject
|
||
|
# Call it as a method.
|
||
|
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)
|