Mercurial > pydertron
annotate pydertron.py @ 22:915fdf283ac5
Moved docs out to a separate file.
| author | Atul Varma <varmaa@toolness.com> |
|---|---|
| date | Thu, 10 Sep 2009 16:59:18 -0700 |
| parents | cb73bb169b67 |
| children | c2c369402a2e |
| rev | line source |
|---|---|
| 18 | 1 # ***** BEGIN LICENSE BLOCK ***** |
| 2 # Version: MPL 1.1/GPL 2.0/LGPL 2.1 | |
| 3 # | |
| 4 # The contents of this file are subject to the Mozilla Public License Version | |
| 5 # 1.1 (the "License"); you may not use this file except in compliance with | |
| 6 # the License. You may obtain a copy of the License at | |
| 7 # http://www.mozilla.org/MPL/ | |
| 8 # | |
| 9 # Software distributed under the License is distributed on an "AS IS" basis, | |
| 10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License | |
| 11 # for the specific language governing rights and limitations under the | |
| 12 # License. | |
| 13 # | |
| 14 # The Original Code is Pydertron. | |
| 15 # | |
| 16 # The Initial Developer of the Original Code is Mozilla. | |
| 17 # Portions created by the Initial Developer are Copyright (C) 2007 | |
| 18 # the Initial Developer. All Rights Reserved. | |
| 19 # | |
| 20 # Contributor(s): | |
| 21 # Atul Varma <atul@mozilla.com> | |
| 22 # | |
| 23 # Alternatively, the contents of this file may be used under the terms of | |
| 24 # either the GNU General Public License Version 2 or later (the "GPL"), or | |
| 25 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), | |
| 26 # in which case the provisions of the GPL or the LGPL are applicable instead | |
| 27 # of those above. If you wish to allow use of your version of this file only | |
| 28 # under the terms of either the GPL or the LGPL, and not to allow others to | |
| 29 # use your version of this file under the terms of the MPL, indicate your | |
| 30 # decision by deleting the provisions above and replace them with the notice | |
| 31 # and other provisions required by the GPL or the LGPL. If you do not delete | |
| 32 # the provisions above, a recipient may use your version of this file under | |
| 33 # the terms of any one of the MPL, the GPL or the LGPL. | |
| 34 # | |
| 35 # ***** END LICENSE BLOCK ***** | |
| 36 | |
| 16 | 37 """ |
|
22
915fdf283ac5
Moved docs out to a separate file.
Atul Varma <varmaa@toolness.com>
parents:
21
diff
changeset
|
38 Pydertron is a high-level wrapper for Pydermonkey that |
| 21 | 39 provides convenient, secure object wrapping between JS and Python |
| 40 space. | |
| 16 | 41 """ |
| 42 | |
| 0 | 43 import sys |
| 44 import threading | |
| 45 import traceback | |
| 46 import weakref | |
| 47 import types | |
|
7
2117265e4dfe
Fixed some threading issues.
Atul Varma <varmaa@toolness.com>
parents:
6
diff
changeset
|
48 import atexit |
| 0 | 49 |
| 50 import pydermonkey | |
| 51 | |
| 52 class ContextWatchdogThread(threading.Thread): | |
| 53 """ | |
| 54 Watches active JS contexts and triggers their operation callbacks | |
| 55 at a regular interval. | |
| 56 """ | |
| 57 | |
| 58 # Default interval, in seconds, that the operation callbacks are | |
| 59 # triggered at. | |
|
8
fb0b161542b1
Improved performance of watchdog thread.
Atul Varma <varmaa@toolness.com>
parents:
7
diff
changeset
|
60 DEFAULT_INTERVAL = 0.25 |
| 0 | 61 |
| 62 def __init__(self, interval=DEFAULT_INTERVAL): | |
| 63 threading.Thread.__init__(self) | |
| 64 self._lock = threading.Lock() | |
| 65 self._stop = threading.Event() | |
| 66 self._contexts = [] | |
| 67 self.interval = interval | |
| 68 | |
| 69 def add_context(self, cx): | |
| 70 self._lock.acquire() | |
| 71 try: | |
| 72 self._contexts.append(weakref.ref(cx)) | |
| 73 finally: | |
| 74 self._lock.release() | |
| 75 | |
| 76 def join(self): | |
| 77 self._stop.set() | |
| 78 threading.Thread.join(self) | |
| 79 | |
| 80 def run(self): | |
| 81 while not self._stop.isSet(): | |
| 82 new_list = [] | |
| 83 self._lock.acquire() | |
| 84 try: | |
| 85 for weakcx in self._contexts: | |
| 86 cx = weakcx() | |
| 87 if cx: | |
| 88 new_list.append(weakcx) | |
| 89 cx.trigger_operation_callback() | |
|
7
2117265e4dfe
Fixed some threading issues.
Atul Varma <varmaa@toolness.com>
parents:
6
diff
changeset
|
90 del cx |
| 0 | 91 self._contexts = new_list |
| 92 finally: | |
| 93 self._lock.release() | |
|
8
fb0b161542b1
Improved performance of watchdog thread.
Atul Varma <varmaa@toolness.com>
parents:
7
diff
changeset
|
94 self._stop.wait(self.interval) |
| 0 | 95 |
| 96 # Create a global watchdog. | |
| 97 watchdog = ContextWatchdogThread() | |
| 98 watchdog.start() | |
|
7
2117265e4dfe
Fixed some threading issues.
Atul Varma <varmaa@toolness.com>
parents:
6
diff
changeset
|
99 atexit.register(watchdog.join) |
| 0 | 100 |
| 101 class InternalError(BaseException): | |
| 102 """ | |
| 103 Represents an error in a JS-wrapped Python function that wasn't | |
| 104 expected to happen; because it's derived from BaseException, it | |
| 105 unrolls the whole JS/Python stack so that the error can be | |
| 106 reported to the outermost calling code. | |
| 107 """ | |
| 108 | |
| 109 def __init__(self): | |
| 110 BaseException.__init__(self) | |
| 111 self.exc_info = sys.exc_info() | |
| 112 | |
| 113 class SafeJsObjectWrapper(object): | |
| 114 """ | |
| 16 | 115 Securely wraps a JS object to behave like any normal Python |
| 116 object. Like JS objects, though, accessing undefined object | |
| 117 results merely in pydermonkey.undefined. | |
| 118 | |
| 119 Object properties may be accessed either via attribute or | |
| 120 item-based lookup. | |
| 0 | 121 """ |
| 122 | |
| 123 __slots__ = ['_jsobject', '_sandbox', '_this'] | |
| 124 | |
| 125 def __init__(self, sandbox, jsobject, this): | |
| 126 if not isinstance(jsobject, pydermonkey.Object): | |
| 127 raise TypeError("Cannot wrap '%s' object" % | |
| 128 type(jsobject).__name__) | |
| 129 object.__setattr__(self, '_sandbox', sandbox) | |
| 130 object.__setattr__(self, '_jsobject', jsobject) | |
| 131 object.__setattr__(self, '_this', this) | |
| 132 | |
| 133 @property | |
| 134 def wrapped_jsobject(self): | |
| 135 return self._jsobject | |
| 136 | |
| 137 def _wrap_to_python(self, jsvalue): | |
| 138 return self._sandbox.wrap_jsobject(jsvalue, self._jsobject) | |
| 139 | |
| 140 def _wrap_to_js(self, value): | |
| 141 return self._sandbox.wrap_pyobject(value) | |
| 142 | |
| 143 def __eq__(self, other): | |
| 144 if isinstance(other, SafeJsObjectWrapper): | |
| 145 return self._jsobject == other._jsobject | |
| 146 else: | |
| 147 return False | |
| 148 | |
| 149 def __str__(self): | |
| 150 return self.toString() | |
| 151 | |
| 152 def __unicode__(self): | |
| 153 return self.toString() | |
| 154 | |
| 155 def __setitem__(self, item, value): | |
| 156 self.__setattr__(item, value) | |
| 157 | |
| 158 def __setattr__(self, name, value): | |
| 159 cx = self._sandbox.cx | |
| 160 jsobject = self._jsobject | |
| 161 | |
| 162 cx.define_property(jsobject, name, | |
| 163 self._wrap_to_js(value)) | |
| 164 | |
| 165 def __getitem__(self, item): | |
| 166 return self.__getattr__(item) | |
| 167 | |
| 168 def __getattr__(self, name): | |
| 169 cx = self._sandbox.cx | |
| 170 jsobject = self._jsobject | |
| 171 | |
| 172 return self._wrap_to_python(cx.get_property(jsobject, name)) | |
| 173 | |
| 174 def __contains__(self, item): | |
| 175 cx = self._sandbox.cx | |
| 176 jsobject = self._jsobject | |
| 177 | |
| 178 return cx.has_property(jsobject, item) | |
| 179 | |
| 180 def __iter__(self): | |
| 181 cx = self._sandbox.cx | |
| 182 jsobject = self._jsobject | |
| 183 | |
| 184 properties = cx.enumerate(jsobject) | |
| 185 for property in properties: | |
| 186 yield property | |
| 187 | |
| 188 class SafeJsFunctionWrapper(SafeJsObjectWrapper): | |
| 189 """ | |
| 190 Securely wraps a JS function to behave like any normal Python object. | |
| 191 """ | |
| 192 | |
| 193 def __init__(self, sandbox, jsfunction, this): | |
| 194 if not isinstance(jsfunction, pydermonkey.Function): | |
| 195 raise TypeError("Cannot wrap '%s' object" % | |
| 196 type(jsobject).__name__) | |
| 197 SafeJsObjectWrapper.__init__(self, sandbox, jsfunction, this) | |
| 198 | |
| 199 def __call__(self, *args): | |
| 200 cx = self._sandbox.cx | |
| 201 jsobject = self._jsobject | |
| 202 this = self._this | |
| 203 | |
| 204 arglist = [] | |
| 205 for arg in args: | |
| 206 arglist.append(self._wrap_to_js(arg)) | |
| 207 | |
| 208 obj = cx.call_function(this, jsobject, tuple(arglist)) | |
| 209 return self._wrap_to_python(obj) | |
| 210 | |
| 11 | 211 def format_stack(js_stack, open=open): |
| 0 | 212 """ |
| 213 Returns a formatted Python-esque stack traceback of the given | |
| 214 JS stack. | |
| 215 """ | |
| 216 | |
| 217 STACK_LINE =" File \"%(filename)s\", line %(lineno)d, in %(name)s" | |
| 218 | |
| 219 lines = [] | |
| 220 while js_stack: | |
| 221 script = js_stack['script'] | |
| 222 function = js_stack['function'] | |
| 223 if script: | |
| 224 frameinfo = dict(filename = script.filename, | |
| 225 lineno = js_stack['lineno'], | |
| 226 name = '<module>') | |
| 227 elif function and not function.is_python: | |
| 228 frameinfo = dict(filename = function.filename, | |
| 229 lineno = js_stack['lineno'], | |
| 230 name = function.name) | |
| 231 else: | |
| 232 frameinfo = None | |
| 233 if frameinfo: | |
| 234 lines.insert(0, STACK_LINE % frameinfo) | |
| 235 try: | |
| 236 filelines = open(frameinfo['filename']).readlines() | |
| 237 line = filelines[frameinfo['lineno'] - 1].strip() | |
| 238 lines.insert(1, " %s" % line) | |
| 239 except Exception: | |
| 240 pass | |
| 241 js_stack = js_stack['caller'] | |
| 242 lines.insert(0, "Traceback (most recent call last):") | |
| 243 return '\n'.join(lines) | |
| 244 | |
| 245 def jsexposed(name=None, on=None): | |
| 246 """ | |
| 247 Decorator used to expose the decorated function or method to | |
| 248 untrusted JS. | |
| 249 | |
| 250 'name' is an optional alternative name for the function. | |
| 251 | |
| 252 'on' is an optional SafeJsObjectWrapper that the function can be | |
| 253 automatically attached as a property to. | |
| 254 """ | |
| 255 | |
| 256 if callable(name): | |
| 257 func = name | |
| 258 func.__jsexposed__ = True | |
| 259 return func | |
| 260 | |
| 261 def make_exposed(func): | |
| 262 if name: | |
| 263 func.__name__ = name | |
| 264 func.__jsexposed__ = True | |
| 265 if on: | |
| 266 on[func.__name__] = func | |
| 267 return func | |
| 268 return make_exposed | |
| 269 | |
| 270 class JsExposedObject(object): | |
| 271 """ | |
| 272 Trivial base/mixin class for any Python classes that choose to | |
| 273 expose themselves to JS code. | |
| 274 """ | |
| 275 | |
| 276 pass | |
| 277 | |
| 278 class JsSandbox(object): | |
| 279 """ | |
| 280 A JS runtime and associated functionality capable of securely | |
| 281 loading and executing scripts. | |
| 282 """ | |
| 283 | |
| 19 | 284 def __init__(self, fs, watchdog=watchdog, opcb=None): |
| 0 | 285 rt = pydermonkey.Runtime() |
| 286 cx = rt.new_context() | |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
287 root_proto = cx.new_object() |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
288 cx.init_standard_classes(root_proto) |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
289 root = cx.new_object(None, root_proto) |
| 0 | 290 |
| 291 cx.set_operation_callback(self._opcb) | |
| 292 cx.set_throw_hook(self._throwhook) | |
| 293 watchdog.add_context(cx) | |
| 294 | |
| 11 | 295 self.fs = fs |
| 19 | 296 self.opcb = opcb |
| 0 | 297 self.rt = rt |
| 298 self.cx = cx | |
| 299 self.curr_exc = None | |
| 300 self.py_stack = None | |
| 301 self.js_stack = None | |
| 11 | 302 self.__modules = {} |
| 0 | 303 self.__py_to_js = {} |
| 304 self.__type_protos = {} | |
| 11 | 305 self.__globals = {} |
| 306 self.__root_proto = root_proto | |
| 0 | 307 self.root = self.wrap_jsobject(root, root) |
| 308 | |
| 11 | 309 def set_globals(self, **globals): |
| 16 | 310 """ |
| 311 Sets the global properties for the root object and all global | |
| 312 scopes (e.g., SecurableModules). This should be called before | |
| 313 any scripts are executed. | |
| 314 """ | |
| 315 | |
| 11 | 316 self.__globals.update(globals) |
| 317 self._install_globals(self.root) | |
| 318 | |
| 0 | 319 def finish(self): |
| 320 """ | |
| 321 Cleans up all resources used by the sandbox, breaking any reference | |
| 322 cycles created due to issue #2 in pydermonkey: | |
| 323 | |
| 324 http://code.google.com/p/pydermonkey/issues/detail?id=2 | |
| 325 """ | |
| 326 | |
| 327 for jsobj in self.__py_to_js.values(): | |
| 328 self.cx.clear_object_private(jsobj) | |
| 329 del self.__py_to_js | |
| 330 del self.__type_protos | |
| 331 del self.curr_exc | |
| 332 del self.py_stack | |
| 333 del self.js_stack | |
| 334 del self.cx | |
| 335 del self.rt | |
| 336 | |
| 337 def _opcb(self, cx): | |
| 19 | 338 # Note that if a keyboard interrupt was triggered, |
| 0 | 339 # it'll get raised here automatically. |
| 19 | 340 if self.opcb: |
| 341 self.opcb() | |
| 0 | 342 |
| 343 def _throwhook(self, cx): | |
| 344 curr_exc = cx.get_pending_exception() | |
| 345 if self.curr_exc != curr_exc: | |
| 346 self.curr_exc = curr_exc | |
| 347 self.py_stack = traceback.extract_stack() | |
| 348 self.js_stack = cx.get_stack() | |
| 349 | |
| 350 def __wrap_pycallable(self, func, pyproto=None): | |
| 351 if func in self.__py_to_js: | |
| 352 return self.__py_to_js[func] | |
| 353 | |
| 354 if hasattr(func, '__name__'): | |
| 355 name = func.__name__ | |
| 356 else: | |
| 357 name = "" | |
| 358 | |
| 359 if pyproto: | |
| 360 def wrapper(func_cx, this, args): | |
| 361 try: | |
| 362 arglist = [] | |
| 363 for arg in args: | |
| 364 arglist.append(self.wrap_jsobject(arg)) | |
| 365 instance = func_cx.get_object_private(this) | |
| 366 if instance is None or not isinstance(instance, pyproto): | |
| 367 raise pydermonkey.error("Method type mismatch") | |
| 368 | |
| 369 # TODO: Fill in extra required params with | |
| 370 # pymonkey.undefined? or automatically throw an | |
| 371 # exception to calling js code? | |
| 372 return self.wrap_pyobject(func(instance, *arglist)) | |
| 373 except pydermonkey.error: | |
| 374 raise | |
| 375 except Exception: | |
| 376 raise InternalError() | |
| 377 else: | |
| 378 def wrapper(func_cx, this, args): | |
| 379 try: | |
| 380 arglist = [] | |
| 381 for arg in args: | |
| 382 arglist.append(self.wrap_jsobject(arg)) | |
| 383 | |
| 384 # TODO: Fill in extra required params with | |
| 385 # pymonkey.undefined? or automatically throw an | |
| 386 # exception to calling js code? | |
| 387 return self.wrap_pyobject(func(*arglist)) | |
| 388 except pydermonkey.error: | |
| 389 raise | |
| 390 except Exception: | |
| 391 raise InternalError() | |
| 392 wrapper.wrapped_pyobject = func | |
| 393 wrapper.__name__ = name | |
| 394 | |
| 395 jsfunc = self.cx.new_function(wrapper, name) | |
| 396 self.__py_to_js[func] = jsfunc | |
| 397 | |
| 398 return jsfunc | |
| 399 | |
| 400 def __wrap_pyinstance(self, value): | |
| 401 pyproto = type(value) | |
| 402 if pyproto not in self.__type_protos: | |
| 403 jsproto = self.cx.new_object() | |
| 404 if hasattr(pyproto, '__jsprops__'): | |
| 405 define_getter = self.cx.get_property(jsproto, | |
| 406 '__defineGetter__') | |
| 407 define_setter = self.cx.get_property(jsproto, | |
| 408 '__defineSetter__') | |
| 409 for name in pyproto.__jsprops__: | |
| 410 prop = getattr(pyproto, name) | |
| 411 if not type(prop) == property: | |
| 412 raise TypeError("Expected attribute '%s' to " | |
| 413 "be a property" % name) | |
| 414 getter = None | |
| 415 setter = None | |
| 416 if prop.fget: | |
| 417 getter = self.__wrap_pycallable(prop.fget, | |
| 418 pyproto) | |
| 419 if prop.fset: | |
| 420 setter = self.__wrap_pycallable(prop.fset, | |
| 421 pyproto) | |
| 422 if getter: | |
| 423 self.cx.call_function(jsproto, | |
| 424 define_getter, | |
| 425 (name, getter)) | |
| 426 if setter: | |
| 427 self.cx.call_function(jsproto, | |
| 428 define_setter, | |
| 429 (name, setter,)) | |
| 430 for name in dir(pyproto): | |
| 431 attr = getattr(pyproto, name) | |
| 432 if (isinstance(attr, types.UnboundMethodType) and | |
| 433 hasattr(attr, '__jsexposed__') and | |
| 434 attr.__jsexposed__): | |
| 435 jsmethod = self.__wrap_pycallable(attr, pyproto) | |
| 436 self.cx.define_property(jsproto, name, jsmethod) | |
| 437 self.__type_protos[pyproto] = jsproto | |
| 438 return self.cx.new_object(value, self.__type_protos[pyproto]) | |
| 439 | |
| 440 def wrap_pyobject(self, value): | |
| 441 """ | |
| 442 Wraps the given Python object for export to untrusted JS. | |
| 443 | |
| 444 If the Python object isn't of a type that can be exposed to JS, | |
| 445 a TypeError is raised. | |
| 446 """ | |
| 447 | |
| 448 if (isinstance(value, (int, basestring, float, bool)) or | |
| 449 value is pydermonkey.undefined or | |
| 450 value is None): | |
| 451 return value | |
| 452 if isinstance(value, SafeJsObjectWrapper): | |
| 453 # It's already wrapped, just unwrap it. | |
| 454 return value.wrapped_jsobject | |
| 455 elif callable(value): | |
| 456 if not (hasattr(value, '__jsexposed__') and | |
| 457 value.__jsexposed__): | |
| 458 raise ValueError("Callable isn't configured for exposure " | |
| 459 "to untrusted JS code") | |
| 460 return self.__wrap_pycallable(value) | |
| 461 elif isinstance(value, JsExposedObject): | |
| 462 return self.__wrap_pyinstance(value) | |
| 463 else: | |
| 464 raise TypeError("Can't expose objects of type '%s' to JS." % | |
| 465 type(value).__name__) | |
| 466 | |
| 467 def wrap_jsobject(self, jsvalue, this=None): | |
| 468 """ | |
| 469 Wraps the given pydermonkey.Object for import to trusted | |
| 470 Python code. If the type is just a primitive, it's simply | |
| 471 returned, since no wrapping is needed. | |
| 472 """ | |
| 473 | |
| 474 if this is None: | |
| 475 this = self.root.wrapped_jsobject | |
| 476 if isinstance(jsvalue, pydermonkey.Function): | |
| 477 if jsvalue.is_python: | |
| 478 # It's a Python function, just unwrap it. | |
| 479 return self.cx.get_object_private(jsvalue).wrapped_pyobject | |
| 480 return SafeJsFunctionWrapper(self, jsvalue, this) | |
| 481 elif isinstance(jsvalue, pydermonkey.Object): | |
| 482 # It's a wrapped Python object instance, just unwrap it. | |
| 483 instance = self.cx.get_object_private(jsvalue) | |
| 484 if instance: | |
| 485 if not isinstance(instance, JsExposedObject): | |
| 486 raise AssertionError("Object private is not of type " | |
| 487 "JsExposedObject") | |
| 488 return instance | |
| 489 else: | |
| 490 return SafeJsObjectWrapper(self, jsvalue, this) | |
| 491 else: | |
| 492 # It's a primitive value. | |
| 493 return jsvalue | |
| 494 | |
| 495 def new_array(self, *contents): | |
| 16 | 496 """ |
| 497 Creates a new JavaScript array with the given contents and | |
| 498 returns a wrapper for it. | |
| 499 """ | |
| 500 | |
| 0 | 501 array = self.wrap_jsobject(self.cx.new_array_object()) |
| 502 for item in contents: | |
| 503 array.push(item) | |
| 504 return array | |
| 505 | |
| 506 def new_object(self, **contents): | |
| 16 | 507 """ |
| 508 Creates a new JavaScript object with the given properties and | |
| 509 returns a wrapper for it. | |
| 510 """ | |
| 511 | |
| 0 | 512 obj = self.wrap_jsobject(self.cx.new_object()) |
| 513 for name in contents: | |
| 514 obj[name] = contents[name] | |
| 515 return obj | |
| 516 | |
| 11 | 517 def get_calling_script(self): |
| 16 | 518 """ |
| 519 Returns the filename of the current stack's most recent | |
| 520 JavaScript caller. | |
| 521 """ | |
| 522 | |
| 11 | 523 frame = self.cx.get_stack()['caller'] |
| 524 curr_script = None | |
| 525 while frame and curr_script is None: | |
| 526 if frame['function'] and frame['function'].filename: | |
| 527 curr_script = frame['function'].filename | |
| 528 elif frame['script']: | |
| 529 curr_script = frame['script'].filename | |
| 530 frame = frame['caller'] | |
| 531 | |
| 532 if curr_script is None: | |
| 533 raise RuntimeError("Can't find calling script") | |
| 534 return curr_script | |
| 535 | |
| 536 def _install_globals(self, object): | |
| 537 for name in self.__globals: | |
| 538 object[name] = self.__globals[name] | |
| 539 object['require'] = self._require | |
| 540 | |
| 541 @jsexposed(name='require') | |
| 542 def _require(self, path): | |
| 16 | 543 """ |
| 544 Implementation for the global require() function, implemented | |
| 545 as per the CommonJS SecurableModule specification: | |
| 546 | |
| 547 http://wiki.commonjs.org/wiki/CommonJS/Modules/SecurableModules | |
| 548 """ | |
| 549 | |
| 11 | 550 filename = self.fs.find_module(self.get_calling_script(), path) |
| 551 if not filename: | |
| 552 raise pydermonkey.error('Module not found: %s' % path) | |
| 553 if not filename in self.__modules: | |
| 554 cx = self.cx | |
| 555 module = cx.new_object(None, self.__root_proto) | |
| 556 cx.init_standard_classes(module) | |
| 557 exports = cx.new_object() | |
| 558 cx.define_property(module, 'exports', exports) | |
| 559 self._install_globals(self.wrap_jsobject(module)) | |
| 560 self.__modules[filename] = self.wrap_jsobject(exports) | |
| 561 contents = self.fs.open(filename).read() | |
| 562 cx.evaluate_script(module, contents, filename, 1) | |
| 563 return self.__modules[filename] | |
| 0 | 564 |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
565 def run_script(self, contents, filename='<string>', lineno=1, |
| 20 | 566 callback=None, stderr=None): |
| 0 | 567 """ |
| 568 Runs the given JS script, returning 0 on success, -1 on failure. | |
| 569 """ | |
| 570 | |
| 20 | 571 if stderr is None: |
| 572 stderr = sys.stderr | |
| 573 | |
| 0 | 574 retval = -1 |
| 575 cx = self.cx | |
| 576 try: | |
| 577 result = cx.evaluate_script(self.root.wrapped_jsobject, | |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
578 contents, filename, lineno) |
| 0 | 579 if callback: |
| 580 callback(self.wrap_jsobject(result)) | |
| 581 retval = 0 | |
| 582 except pydermonkey.error, e: | |
| 11 | 583 params = dict( |
| 584 stack_trace = format_stack(self.js_stack, self.fs.open), | |
| 585 error = e.args[1] | |
| 586 ) | |
| 587 stderr.write("%(stack_trace)s\n%(error)s\n" % params) | |
| 0 | 588 except InternalError, e: |
| 11 | 589 stderr.write("An internal error occurred.\n") |
| 590 traceback.print_tb(e.exc_info[2], None, stderr) | |
| 591 stderr.write("%s\n" % e.exc_info[1]) | |
| 0 | 592 return retval |
|
1
ab09b8a10876
Added trivial half-baked implementation of securable modules.
Atul Varma <varmaa@toolness.com>
parents:
0
diff
changeset
|
593 |
| 14 | 594 class HttpFileSystem(object): |
| 16 | 595 """ |
| 596 File system through which all resources are loaded over HTTP. | |
| 597 """ | |
| 598 | |
| 14 | 599 def __init__(self, base_url): |
| 600 self.base_url = base_url | |
| 601 | |
| 602 def find_module(self, curr_url, path): | |
| 603 import urlparse | |
| 604 | |
| 605 if path.startswith("."): | |
| 606 base_url = curr_url | |
| 607 else: | |
| 608 base_url = self.base_url | |
| 609 | |
| 610 url = "%s.js" % urlparse.urljoin(base_url, path) | |
| 611 if not url.startswith(self.base_url): | |
| 612 return None | |
| 613 return url | |
| 614 | |
| 615 def open(self, url): | |
| 616 import urllib | |
| 617 | |
| 618 return urllib.urlopen(url) | |
| 619 | |
| 16 | 620 class LocalFileSystem(object): |
| 621 """ | |
| 622 File system through which all resources are loaded over the local | |
| 623 filesystem. | |
| 624 """ | |
| 625 | |
| 11 | 626 def __init__(self, root_dir): |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
627 self.root_dir = root_dir |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
628 |
| 11 | 629 def find_module(self, curr_script, path): |
| 14 | 630 import os |
| 631 | |
| 11 | 632 if path.startswith("."): |
| 633 base_dir = os.path.dirname(curr_script) | |
| 634 else: | |
| 635 base_dir = self.root_dir | |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
636 |
| 11 | 637 ospath = path.replace('/', os.path.sep) |
| 638 filename = os.path.join(base_dir, "%s.js" % ospath) | |
| 639 filename = os.path.normpath(filename) | |
| 640 if (filename.startswith(self.root_dir) and | |
| 641 (os.path.exists(filename) and | |
| 642 not os.path.isdir(filename))): | |
| 643 return filename | |
| 644 else: | |
| 645 return None | |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
646 |
| 11 | 647 def open(self, filename): |
| 648 return open(filename, 'r') |
