not declaring some/all params. git-svn-id: svn://10.0.0.236/branches/DOM_AGNOSTIC_BRANCH@181898 18797224-902f-48f8-a5cc-f745e15eee43
279 lines
12 KiB
Python
279 lines
12 KiB
Python
# This is vaguely related to an nsIScriptContext. The pydom C code
|
|
# just calls back into this class - but that C code still retains some of the
|
|
# functionality.
|
|
|
|
import types, new, logging
|
|
|
|
import domcompile
|
|
|
|
from xpcom.client import Component
|
|
from xpcom import components, primitives, COMException, nsError
|
|
|
|
import _nsdom
|
|
|
|
IID_nsIScriptGlobalObject = _nsdom.IID_nsIScriptGlobalObject
|
|
|
|
# Borrow the xpcom logger (but use a new sub-logger)
|
|
logger = logging.getLogger("xpcom.nsdom")
|
|
# Note that may calls to logger.debug are prefixed with if __debug__.
|
|
# This is to prevent *any* penalty in non-debug mode for logging in perf
|
|
# critical areas.
|
|
|
|
# Also note that assert statements have no cost in non-debug builds.
|
|
|
|
# The event listener class we attach to an object for addEventListener
|
|
class EventListener:
|
|
_com_interfaces_ = components.interfaces.nsIDOMEventListener
|
|
def __init__(self, handler, globs = None):
|
|
try:
|
|
self.co = handler.func_code
|
|
except AttributeError:
|
|
self.co = compile(handler, "inline event", "exec")
|
|
self.globals = globs or globals()
|
|
|
|
def handleEvent(self, event):
|
|
exec self.co in self.globals
|
|
|
|
class WrappedNative(Component):
|
|
"""Implements the xpconnect concept of 'wrapped natives' and 'expandos'.
|
|
|
|
DOM objects can have arbitrary values set on them. Once this is done for
|
|
the first time, it gets stored in a map in the context. This leads to
|
|
cycles, which must be cleaned up when the context is closed.
|
|
"""
|
|
def __init__(self, context, obj):
|
|
# Store our context - but our context doesn't keep a reference
|
|
# to us until we call _remember_object() on the context.
|
|
self.__dict__['_context_'] = context
|
|
# We store expandos in a seperate dict rather than directly in our
|
|
# __dict__. No real need for this other than to prevent these
|
|
# attributes clobbering ones we need to work!
|
|
self.__dict__['_expandos_'] = {}
|
|
Component.__init__(self, obj)
|
|
|
|
def __repr__(self):
|
|
return "<XPCOM DOM object wrapping %s>" % (self._comobj_,)
|
|
|
|
def __getattr__(self, attr):
|
|
# If it exists in expandos, always return it.
|
|
if attr.startswith("__"):
|
|
raise AttributeError, attr
|
|
# expandos come before interface attributes (which may be wrong. If
|
|
# we do this, why not just store it in __dict__?)
|
|
expandos = self._expandos_
|
|
if expandos.has_key(attr):
|
|
return expandos[attr]
|
|
return Component.__getattr__(self, attr)
|
|
|
|
def __setattr__(self, attr, value):
|
|
try:
|
|
Component.__setattr__(self, attr, value)
|
|
except AttributeError:
|
|
# Set it as an 'expando'. It looks like we should delegate *all*
|
|
# to the outside object.
|
|
logger.debug("%s set expando property %r=%r for object %r",
|
|
self, attr, value, self._comobj_)
|
|
# and register if an event.
|
|
if attr.startswith("on"):
|
|
# I'm quite confused by this :(
|
|
target = self._comobj_
|
|
if _nsdom.IsOuterWindow(target):
|
|
target = _nsdom.GetCurrentInnerWindow(target)
|
|
go = self._context_.globalObject
|
|
scope = self._context_.GetNativeGlobal()
|
|
if callable(value):
|
|
# no idea if this is right - set the compiled object ourselves.
|
|
self._expandos_[attr] = value
|
|
_nsdom.RegisterScriptEventListener(go, scope, target, attr)
|
|
else:
|
|
_nsdom.AddScriptEventListener(target, attr, value, False, False)
|
|
_nsdom.CompileScriptEventListener(go, scope, target, attr)
|
|
else:
|
|
self._expandos_[attr] = value
|
|
self._context_._remember_object(self)
|
|
|
|
def addEventListener(self, event, handler, useCapture=False):
|
|
# We need to transform string or function objects into
|
|
# nsIDOMEventListener interfaces.
|
|
if not hasattr(handler, "handleEvent"): # may already be a handler instance.
|
|
# Wrap it in our instance, which knows how to convert
|
|
handler = EventListener(handler, self._context_.GetNativeGlobal())
|
|
|
|
base = self.__getattr__('addEventListener')
|
|
base(event, handler, useCapture)
|
|
|
|
class WrappedNativeGlobal(WrappedNative):
|
|
# Special support for our global.
|
|
def __repr__(self):
|
|
return "<GlobalWindow (outer=%s) %r>" % (_nsdom.IsOuterWindow(self._comobj_),
|
|
self._comobj_)
|
|
|
|
class ScriptContext:
|
|
def __init__(self):
|
|
self.globalNamespace = {} # must not change identity!
|
|
self._reset()
|
|
|
|
def _reset(self):
|
|
self._remembered_objects_ = {}
|
|
self.globalObject = None
|
|
self.globalNamespace.clear()
|
|
|
|
def __repr__(self):
|
|
return "<ScriptContext at %d>" % id(self)
|
|
|
|
# Called by the _nsdom C++ support to wrap the DOM objects.
|
|
def MakeInterfaceResult(self, object, iid):
|
|
if __debug__:
|
|
logger.debug("MakeInterfaceResult for %r (remembered=%s)",
|
|
object,
|
|
self._remembered_objects_.has_key(object))
|
|
assert not hasattr(object, "_comobj_"), "should not be wrapped!"
|
|
# If it is remembered, just return that object.
|
|
try:
|
|
return self._remembered_objects_[object]
|
|
except KeyError:
|
|
# Make a new wrapper - but don't remember the wrapper until
|
|
# we need to, when a property is set on it.
|
|
|
|
# We should probably QI for nsIClassInfo, and only do this special
|
|
# wrapping for objects with the DOM flag set.
|
|
|
|
if iid == IID_nsIScriptGlobalObject:
|
|
klass = WrappedNativeGlobal
|
|
else:
|
|
klass = WrappedNative
|
|
return klass(self, object)
|
|
|
|
def _remember_object(self, object):
|
|
# You must only try and remember a wrapped object.
|
|
# Once an object has been wrapped once, all further requests must
|
|
# be identical
|
|
assert self._remembered_objects_.get(object._comobj_, object)==object, \
|
|
"Previously remembered object is not this object!"
|
|
self._remembered_objects_[object._comobj_] = object
|
|
logger.debug("%s remembering object %r - now %d items", self, object,
|
|
len(self._remembered_objects_))
|
|
|
|
def _fixArg(self, arg):
|
|
try:
|
|
argv = arg.QueryInterface(components.interfaces.nsIArray)
|
|
except COMException, why:
|
|
if why.errno != nsError.NS_NOINTERFACE:
|
|
raise
|
|
# This is not an array - see if it is a primitive.
|
|
try:
|
|
return primitives.GetPrimitive(arg)
|
|
except COMException, why:
|
|
if why.errno != nsError.NS_NOINTERFACE:
|
|
raise
|
|
return arg
|
|
# Its an array - do each item
|
|
ret = []
|
|
for i in range(argv.length):
|
|
val = argv.queryElementAt(i, components.interfaces.nsISupports)
|
|
ret.append(self._fixArg(val))
|
|
return ret
|
|
|
|
def GetNativeGlobal(self):
|
|
return self.globalNamespace
|
|
|
|
def InitContext(self, globalObject):
|
|
self._reset()
|
|
self.globalObject = globalObject
|
|
if globalObject is None:
|
|
logger.debug("%r initializing with NULL global, ns=%d", self,
|
|
id(self.globalNamespace))
|
|
else:
|
|
assert isinstance(globalObject, WrappedNativeGlobal), \
|
|
"Out global should have been wrapped in WrappedNativeGlobal"
|
|
if __debug__:
|
|
logger.debug("%r initializing (outer=%s), ns=%d", self,
|
|
_nsdom.IsOuterWindow(globalObject),
|
|
id(self.globalNamespace))
|
|
ns = globalObject.__dict__['_expandos_'] = self.globalNamespace
|
|
# Do an optimized 'setattr' for the global
|
|
self._remember_object(globalObject)
|
|
ns['this'] = globalObject
|
|
# How is this magic supposed to work? What others are there?
|
|
ns['window'] = globalObject
|
|
try:
|
|
ns['document'] = globalObject.document
|
|
except AttributeError:
|
|
logger.warning("'this' object has no document global")
|
|
|
|
def FinalizeClasses(self, globalObject):
|
|
self._reset()
|
|
|
|
def ExecuteScript(self, scriptObject, scopeObject):
|
|
if __debug__:
|
|
logger.debug("%s.ExecuteScript %r in scope %s",
|
|
self, scriptObject, id(scopeObject))
|
|
globals = self.GetNativeGlobal()
|
|
assert globals is not None
|
|
assert scopeObject is None or scopeObject is globals, \
|
|
"Global was changed??"
|
|
if scopeObject is None:
|
|
scopeObject = globals
|
|
assert type(scopeObject)==dict, "expecting scope to be a dict!"
|
|
assert type(scriptObject) == types.CodeType, \
|
|
"Script object should be a code object (got %r)" % (scriptObject,)
|
|
exec scriptObject in scopeObject
|
|
|
|
def CompileScript(self, text, scopeObject, principal, url, lineno, version):
|
|
# The line number passed is the first; offset is -1
|
|
return domcompile.compile(text, url, lineno=lineno-1)
|
|
|
|
def CompileEventHandler(self, principal, name, argNames, body, url, lineno):
|
|
co = domcompile.compile_function(body, url, name, argNames,
|
|
lineno=lineno-1)
|
|
g = {}
|
|
exec co in g
|
|
return g[name]
|
|
|
|
def CallEventHandler(self, target, scope, handler, argv):
|
|
if __debug__:
|
|
logger.debug("CallEventHandler %r on target %s in scope %s",
|
|
handler, target, id(scope))
|
|
assert scope is self.GetNativeGlobal(), "scope was changed??"
|
|
globs = self.globalObject._expandos_.copy()
|
|
globs['this'] = target # XXX - what should this be exposed as?
|
|
# Although handler is already a function object, we must re-bind to
|
|
# new globals
|
|
f = new.function(handler.func_code, globs, handler.func_name)
|
|
args = tuple(self._fixArg(argv))
|
|
# We support having less args declared than supplied, a-la JS.
|
|
args = args[:handler.func_code.co_argcount]
|
|
return f(*args)
|
|
|
|
def BindCompiledEventHandler(self, target, scope, name, handler):
|
|
if __debug__:
|
|
logger.debug("%s.BindCompiledEventHandler (%s=%r) on target %s in scope %s",
|
|
self, name, handler, target, id(scope))
|
|
assert scope is self.GetNativeGlobal(), "scope was changed??"
|
|
# Keeps a ref to both the target and handler.
|
|
ns = target._expandos_
|
|
ns[name] = handler
|
|
self._remember_object(target)
|
|
|
|
def GetBoundEventHandler(self, target, scope, name):
|
|
if __debug__:
|
|
logger.debug("%s.GetBoundEventHandler for '%s' on target %s in scope %s",
|
|
self, name, target, id(scope))
|
|
assert scope is self.GetNativeGlobal(), "scope was changed??"
|
|
# No handler by that name? Just let the py exception propogate - it
|
|
# really should already have been bound, so we don't want it silent.
|
|
# EEEK - this has something to do with inner vs outer windows
|
|
# and is almost certainly not correct
|
|
ns = target._expandos_
|
|
try:
|
|
return ns[name]
|
|
except KeyError:
|
|
logger.info("Event handler %s not found in my namespace - checking scope",
|
|
name)
|
|
return scope[name]
|
|
|
|
def SetProperty(self, target, name, value):
|
|
if __debug__:
|
|
logger.debug("%s.SetProperty for %s=%r", self, name, value)
|
|
target[name] = self._fixArg(value)
|