forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			499 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			499 lines
		
	
	
	
		
			22 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 sys, types, new, logging
 | |
| 
 | |
| import domcompile
 | |
| 
 | |
| from xpcom.client import Component
 | |
| from xpcom import components, primitives, COMException, nsError, _xpcom
 | |
| 
 | |
| import _nsdom
 | |
| 
 | |
| GlobalWindowIIDs = [_nsdom.IID_nsIScriptGlobalObject,
 | |
|                     components.interfaces.nsIDOMWindow]
 | |
| 
 | |
| IID_nsIDOMXULElement = components.interfaces.nsIDOMXULElement
 | |
| 
 | |
| # Borrow the xpcom logger (but use a new sub-logger)
 | |
| logger = logging.getLogger("xpcom.nsdom")
 | |
| # Note that many 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.
 | |
| 
 | |
| # XUL Elements don't expose the XBL implemented interfaces via classinfo.
 | |
| # So we cheat :)
 | |
| # Most support nsIAccessibleProvider - don't list that!
 | |
| # Although this sucks for now, if you strike something that isn't listed,
 | |
| # then simply QI the object you have for the interface you need to use. To
 | |
| # avoid the explicit QI, you could also have your code do:
 | |
| #    import nsdom.context
 | |
| #    xei = nsdom.context.xul_element_interfaces
 | |
| #    # flag for manual inspection should a future version include it
 | |
| #    if 'somewidget' in xei: raise RuntimeError, "already here!?"
 | |
| #    xei['somewidget'] = [...]
 | |
| 
 | |
| xul_element_interfaces = {
 | |
|     # tagName:      [IID, ...]
 | |
|     'textbox':      [components.interfaces.nsIDOMXULTextBoxElement],
 | |
|     'button':       [components.interfaces.nsIDOMXULButtonElement],
 | |
|     'checkbox':     [components.interfaces.nsIDOMXULCheckboxElement],
 | |
|     'image':        [components.interfaces.nsIDOMXULImageElement],
 | |
|     'tree':         [components.interfaces.nsIDOMXULTreeElement,
 | |
|                      components.interfaces.nsIDOMXULMultiSelectControlElement],
 | |
|     'listbox':      [components.interfaces.nsIDOMXULMultiSelectControlElement],
 | |
|     'menu':         [components.interfaces.nsIDOMXULSelectControlItemElement],
 | |
|     'popup':        [components.interfaces.nsIDOMXULPopupElement],
 | |
|     'radiogroup':   [components.interfaces.nsIDOMXULSelectControlElement],
 | |
| }
 | |
| 
 | |
| def dump(fmt, *args):
 | |
|     """A global 'dump' function, available in global namespaces.  Similar to
 | |
|     The JS version, but this supports % message formatting.
 | |
| 
 | |
|     Main advantage over 'print' is that it catches the possible IO error
 | |
|     when there is no available console.
 | |
|     """
 | |
|     try:
 | |
|         sys.stderr.write(fmt % args)
 | |
|         sys.stderr.write("\n")
 | |
|     except IOError:
 | |
|         pass
 | |
| 
 | |
| # The event listener class we attach to an object for addEventListener
 | |
| class EventListener:
 | |
|     _com_interfaces_ = components.interfaces.nsIDOMEventListener
 | |
|     def __init__(self, context, evt_name, handler, globs):
 | |
|         # 'globs' are the globals the event handler will use, or None
 | |
|         # if the handler should use the globals it was compiled with.
 | |
|         self.context = context
 | |
|         if callable(handler):
 | |
|             self.func = handler
 | |
|         else:
 | |
|             # event name for addEventListener has no 'on' prefix
 | |
|             func_name = "on" + evt_name
 | |
|             # What to do about arg names?  It looks like onerror
 | |
|             # still only gets one arg here anyway - handleEvent only takes
 | |
|             # one!
 | |
|             arg_names = ('event',)
 | |
|             co = domcompile.compile_function(handler,
 | |
|                                              "inline event '%s'" % evt_name,
 | |
|                                              func_name,
 | |
|                                              arg_names)
 | |
|             # Could use globs here, but should not be necessary - all we
 | |
|             # are doing is getting the function object from it.
 | |
|             g = {}
 | |
|             exec co in g
 | |
|             self.func = g[func_name]
 | |
|         self.globals = globs
 | |
| 
 | |
|     def handleEvent(self, event):
 | |
|         # Although handler is already a function object, we must re-bind to
 | |
|         # new globals if necessary.
 | |
|         if self.globals is not None:
 | |
|             f = new.function(self.func.func_code, self.globals, 
 | |
|                             self.func.func_name, self.func.func_closure)
 | |
|         else:
 | |
|             f = self.func
 | |
|         # Convert the raw pyxpcom object to a magic _nsdom one, that
 | |
|         # knows how to remember expandos etc based on context (ie, winds
 | |
|         # up calling back into MakeInterfaceResult().
 | |
|         event = _nsdom.MakeDOMObject(self.context, event)
 | |
|         
 | |
|         args = (event,)
 | |
|         # We support having less args declared than supplied, a-la JS.
 | |
|         # (This can only happen when passed a function object - we always
 | |
|         # compile a string handler into a function with 1 arg)
 | |
|         args = args[:f.func_code.co_argcount]
 | |
|         return f(*args)
 | |
| 
 | |
| 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, iid):
 | |
|         # Store our context - but our context doesn't keep a reference
 | |
|         # to us until someone calls self._remember_object() on the context -
 | |
|         # which sets up all kinds of cycles!
 | |
|         self.__dict__['_context_'] = context
 | |
|         # We store expandos in a separate 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, iid)
 | |
| 
 | |
|     def __repr__(self):
 | |
|         iface_desc = self._get_classinfo_repr_()
 | |
|         return "<DOM object '%s' (%s)>" % (self._object_name_,iface_desc)
 | |
|         
 | |
|     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("setting expando %s.%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 _build_all_supported_interfaces_(self):
 | |
|         # Generally called as pyxpcom is finding an attribute, overridden to
 | |
|         # work around lack of class info for xbl bindings.
 | |
|         Component._build_all_supported_interfaces_(self)
 | |
|         # Now hard-code certain element names we know about, as the XBL
 | |
|         # implemented interfaces are not exposed by this.
 | |
|         ii = self.__dict__['_interface_infos_']
 | |
|         if ii.has_key(IID_nsIDOMXULElement):
 | |
|             # Is a DOM element - see if in our map.
 | |
|             tagName = self.tagName
 | |
|             interfaces = xul_element_interfaces.get(tagName, [])
 | |
|             for interface in interfaces:
 | |
|                 if not ii.has_key(interface):
 | |
|                         self._remember_interface_info(interface)
 | |
|         else:
 | |
|             logger.info("Not a DOM element - not hooking extra interfaces")
 | |
| 
 | |
|     def addEventListener(self, event, handler, useCapture=False, globs=None):
 | |
|         # We need to transform string or function objects into
 | |
|         # nsIDOMEventListener interfaces.
 | |
|         logger.debug("addEventListener for %r, event=%r, handler=%r, cap=%s",
 | |
|                      self, event, handler, useCapture)
 | |
| 
 | |
|         if not hasattr(handler, "handleEvent"): # may already be a handler instance.
 | |
|             # Wrap it in our instance, which knows how to convert strings and
 | |
|             # function objects.
 | |
| 
 | |
|             # Handle semantics for event handler globals:
 | |
|             # * If a function, then function globals are used, as per normal.
 | |
|             # * If a string object and no explicit globals param, use the
 | |
|             #   caller's globals
 | |
|             if globs is None and not callable(handler):
 | |
|                 globs = sys._getframe().f_back.f_globals
 | |
|             handler = EventListener(self._context_, event, handler, globs)
 | |
|         else:
 | |
|             assert not globs, "can't specify a handler instance and globals"
 | |
| 
 | |
|         base = self.__getattr__('addEventListener')
 | |
|         base(event, handler, useCapture)
 | |
| 
 | |
| class WrappedNativeGlobal(WrappedNative):
 | |
|     # Special support for our global object.  Certain methods exposed by
 | |
|     # IDL are JS specific - generally ones that take a variable number of args,
 | |
|     # a concept that doesn't exist in xpcom.
 | |
|     def __repr__(self):
 | |
|         iface_desc = self._get_classinfo_repr_()
 | |
|         outer = _nsdom.IsOuterWindow(self._comobj_)
 | |
|         return "<GlobalWindow outer=%s %s>" % (outer, iface_desc)
 | |
| 
 | |
|     # Open window/dialog
 | |
|     def openDialog(self, url, name, features="", *args):
 | |
|         svc = components.classes['@mozilla.org/embedcomp/window-watcher;1'] \
 | |
|                 .getService(components.interfaces.nsIWindowWatcher)
 | |
|         # Wrap in an nsIArray with special support for Python being able to
 | |
|         # access the raw objects if the call-site is smart enough
 | |
|         args = _nsdom.MakeArray(args)
 | |
|         ret = svc.openWindow(self._comobj_, url, name, features, args)
 | |
|         # and re-wrap in one of our "dom" objects
 | |
|         return _nsdom.MakeDOMObject(self._context_, ret)
 | |
| 
 | |
|     # window.open and window.openDialog seem identical except for *args???
 | |
|     open = openDialog
 | |
| 
 | |
|     # Timeout related functions.
 | |
|     def setTimeout(self, interval, handler, *args):
 | |
|         return _nsdom.SetTimeoutOrInterval(self._comobj_, interval, handler,
 | |
|                                            args, False)
 | |
| 
 | |
|     # setInterval appears to have reversed args???
 | |
|     def setInterval(self, handler, interval, *args):
 | |
|         return _nsdom.SetTimeoutOrInterval(self._comobj_, interval, handler,
 | |
|                                            args, True)
 | |
| 
 | |
|     def clearTimeout(self, tid):
 | |
|         return _nsdom.ClearTimeoutOrInterval(self._comobj_, tid)
 | |
| 
 | |
|     def clearInterval(self, tid):
 | |
|         return _nsdom.ClearTimeoutOrInterval(self._comobj_, tid)
 | |
| 
 | |
| # Our "script context" - morally an nsIScriptContext, although that lives
 | |
| # in our C++ code and delegates to this.
 | |
| class ScriptContext:
 | |
|     def __init__(self):
 | |
|         self.globalNamespace = {} # must not change identity!
 | |
|         self._remembered_objects_ = {} # could, but doesn't
 | |
|         self._reset()
 | |
| 
 | |
|     def _reset(self):
 | |
|         # Explicitly wipe all 'expandos' for our remembered objects.
 | |
|         # Its not clear this is necessary, but it is easy to envisage someone
 | |
|         # setting up a cycle via expandos.
 | |
|         for ro in self._remembered_objects_.itervalues():
 | |
|             ro._expandos_.clear()
 | |
|         self._remembered_objects_.clear()
 | |
|         # self.globalObject is the "outer window" global, whereas the
 | |
|         # inner window global tends to be passed in to each function as the
 | |
|         # 'scope' object.
 | |
|         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 0 and __debug__: # this is a little noisy...
 | |
|             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 in GlobalWindowIIDs:
 | |
|                 klass = WrappedNativeGlobal
 | |
|             else:
 | |
|                 klass = WrappedNative
 | |
|             return klass(self, object, iid)
 | |
| 
 | |
|     # Called whenever this object must be "permanently" attached to the
 | |
|     # context.  Typically this means an attribute or event handler
 | |
|     # has been set on the object, which should "persist" while the context
 | |
|     # is alive (ie, future requests to getElementById(), for example, must
 | |
|     # return the same underlying object, with event handlers and properties
 | |
|     # still in-place.
 | |
|     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
 | |
|         if __debug__:
 | |
|             logger.debug("%s remembering object %r - now %d items", self,
 | |
|                          object, len(self._remembered_objects_))
 | |
| 
 | |
|     def _fixArg(self, arg):
 | |
|         if arg is None:
 | |
|             return []
 | |
|         # Already a tuple?  This means the original Python args have been
 | |
|         # found and passed directly.
 | |
|         if type(arg) == types.TupleType:
 | |
|             return 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 variant or primitive.
 | |
|             try:
 | |
|                 var = arg.queryInterface(components.interfaces.nsIVariant)
 | |
|                 parent = None
 | |
|                 if self.globalObject is not None:
 | |
|                     parent = self.globalObject._comobj_
 | |
|                 if parent is None:
 | |
|                     logger.warning("_fixArg for context with no global??")
 | |
|                 return _xpcom.GetVariantValue(var, parent)
 | |
|             except COMException, why:
 | |
|                 if why.errno != nsError.NS_NOINTERFACE:
 | |
|                     raise
 | |
|                 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 CreateNativeGlobalForInner(self, globalObject, isChrome):
 | |
|         ret = dict()
 | |
|         if __debug__:
 | |
|             logger.debug("%r CreateNativeGlobalForInner returning %d",
 | |
|                          self, id(ret))
 | |
|         return ret
 | |
| 
 | |
|     def ConnectToInner(self, newInner, globalScope, innerScope):
 | |
|         if __debug__:
 | |
|             logger.debug("%r ConnectToInner(%r, %d, %d)",
 | |
|                          self, newInner, id(globalScope), id(innerScope))
 | |
|         globalScope['_inner_'] = innerScope # will do for now.
 | |
| 
 | |
|         self._prepareGlobalNamespace(newInner, innerScope)
 | |
| 
 | |
|     def WillInitializeContext(self):
 | |
|         if __debug__:
 | |
|             logger.debug("%r WillInitializeContent", self)
 | |
| 
 | |
|     def DidInitializeContext(self):
 | |
|         if __debug__:
 | |
|             logger.debug("%r DidInitializeContent", self)
 | |
| 
 | |
|     def DidSetDocument(self, doc, scope):
 | |
|         if __debug__:
 | |
|             logger.debug("%r DidSetDocument doc=%r scope=%d",
 | |
|                          self, doc, id(scope))
 | |
|         scope['document'] = doc
 | |
| 
 | |
|     def _prepareGlobalNamespace(self, globalObject, globalNamespace):
 | |
|         assert isinstance(globalObject, WrappedNativeGlobal), \
 | |
|                "Our global should have been wrapped in WrappedNativeGlobal"
 | |
|         if __debug__:
 | |
|             logger.debug("%r initializing (outer=%s), ns=%d", self,
 | |
|                          _nsdom.IsOuterWindow(globalObject),
 | |
|                          id(globalNamespace))
 | |
|         assert len(globalObject._expandos_)==0, \
 | |
|                "already initialized this global?"
 | |
|         ns = globalObject.__dict__['_expandos_'] = globalNamespace
 | |
|         self._remember_object(globalObject)
 | |
|         ns['window'] = globalObject
 | |
|         # we can't fetch 'document' and stash it now - there may not be one
 | |
|         # at this instant (ie, it may be in the process of being attached)
 | |
|         # Poke some other utilities etc into the namespace.
 | |
|         ns['dump'] = dump
 | |
| 
 | |
|     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:
 | |
|             self._prepareGlobalNamespace(globalObject, self.globalNamespace)
 | |
| 
 | |
|     def FinalizeClasses(self, globalObject):
 | |
|         self._reset()
 | |
| 
 | |
|     def ClearScope(self, globalObject):
 | |
|         if __debug__:
 | |
|             logger.debug("%s.ClearScope (%d)", self, id(globalObject))
 | |
|         globalObject.clear()
 | |
| 
 | |
|     def FinalizeContext(self):
 | |
|         if __debug__:
 | |
|             logger.debug("%s.FinalizeContext", self)
 | |
|         self._reset()
 | |
| 
 | |
|     def EvaluateString(self, script, glob, principal, url, lineno, ver):
 | |
|         # This appears misnamed - return value it not used.  Therefore we can
 | |
|         # just do a simple 'exec'.  If we needed a return value, we would have
 | |
|         # to treat this like a string event-handler, and compile as a temp
 | |
|         # function.
 | |
|         assert type(glob) == types.DictType
 | |
|         # compile then exec to make use of lineno
 | |
|         co = domcompile.compile(script, url, lineno=lineno-1)
 | |
|         exec co in glob
 | |
| 
 | |
|     def ExecuteScript(self, scriptObject, scopeObject):
 | |
|         if __debug__:
 | |
|             logger.debug("%s.ExecuteScript %r in scope %s",
 | |
|                          self, scriptObject, id(scopeObject))
 | |
|         if scopeObject is None:
 | |
|             scopeObject = self.GetNativeGlobal()
 | |
|         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, name, argNames, body, url, lineno):
 | |
|         if __debug__:
 | |
|             logger.debug("%s.CompileEventHandler %s %s:%s ('%s')",
 | |
|                          self, name, url, lineno, body[:100])
 | |
|         co = domcompile.compile_function(body, url, name, argNames,
 | |
|                                          lineno=lineno-1)
 | |
|         g = {}
 | |
|         exec co in g
 | |
|         handler = g[name]
 | |
|         # Flag that this function started life as inline script code,
 | |
|         # so we later ensure the correct globals are used when the event fires
 | |
|         handler._nsdom_compiled = True
 | |
|         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))
 | |
|         # Event handlers must fire in their real globals (not a copy) so
 | |
|         # it can set global vars!
 | |
|         # If this was an inline event handler, re-bind to the window's globals.
 | |
|         if hasattr(handler, '_nsdom_compiled'):
 | |
|             globs = scope
 | |
|             # re-bind to new globals
 | |
|             f = new.function(handler.func_code, globs, handler.func_name,
 | |
|                              handler.func_defaults)
 | |
|             handler = f
 | |
|         args = tuple(self._fixArg(argv))
 | |
|         # We support having less args declared than supplied, a-la JS.
 | |
|         args = args[:handler.func_code.co_argcount]
 | |
|         return handler(*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))
 | |
|         # Keeps a ref to both the target and handler.
 | |
|         self._remember_object(target)
 | |
|         ns = target._expandos_
 | |
|         ns[name] = handler
 | |
| 
 | |
|     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))
 | |
|         ns = target._expandos_
 | |
|         return ns[name]
 | |
| 
 | |
|     def SetProperty(self, target, name, value):
 | |
|         if __debug__:
 | |
|             logger.debug("%s.SetProperty for %s=%r", self, name, value)
 | |
|         target[name] = self._fixArg(value)
 | 
