forked from panda3d/panda3d
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJavaScript.py
More file actions
298 lines (244 loc) · 11.7 KB
/
JavaScript.py
File metadata and controls
298 lines (244 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
""" 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. """
__all__ = ["UndefinedObject", "Undefined", "ConcreteStruct", "BrowserObject", "MethodWrapper"]
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 __bool__(self):
return False
__nonzero__ = __bool__ # Python 2
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 list(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 __bool__(self):
return True
__nonzero__ = __bool__ # Python 2
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], str):
# 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, str):
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, str):
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, str):
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 __bool__(self):
return True
__nonzero__ = __bool__ # Python 2
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], str):
# 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)