Skip to content

Commit

Permalink
webkit bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
thepian committed Jul 19, 2010
1 parent 114ec80 commit ca13af9
Show file tree
Hide file tree
Showing 8 changed files with 698 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Library/thepianpython/webkit_bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
This package provides a bridge between WebKit and a PyPy-based interpreter.

To use, please create a symlink to the WebKit build as `webkit`. It should
contain a debug build and libJavaScriptCore.so. To create libJavaScriptCore.so,
go to WebKitBuild/Debug/.libs and perform:

$ ar x libJavaScriptCore.a
$ g++ -shared -o libJavaScriptCore.so *.o -lpthread -lglib-2.0 `icu-config --ldflags`

And, finally, make sure the .so is on your LD_LIBRARY_PATH.
4 changes: 4 additions & 0 deletions Library/thepianpython/webkit_bridge/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# No Copyright (-) 2009-2010 The Ampify Authors. This file is under the
# Public Domain license that can be found in the root LICENSE file.

"""A webkit bridge for PyPy-based interpreters."""
4 changes: 4 additions & 0 deletions Library/thepianpython/webkit_bridge/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# No Copyright (-) 2009-2010 The Ampify Authors. This file is under the
# Public Domain license that can be found in the root LICENSE file.

from pypy.conftest import Directory, Module, option, ConftestPlugin
156 changes: 156 additions & 0 deletions Library/thepianpython/webkit_bridge/jsobj.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# No Copyright (-) 2009-2010 The Ampify Authors. This file is under the
# Public Domain license that can be found in the root LICENSE file.

from pypy.interpreter.baseobjspace import Wrappable, W_Root, ObjSpace
from pypy.interpreter.gateway import interp2app
from pypy.interpreter.typedef import TypeDef
from pypy.interpreter.error import OperationError
from webkit_bridge.webkit_rffi import *
from pypy.rpython.lltypesystem import lltype, rffi
from pypy.interpreter.argument import Arguments
from pypy.rpython.memory.support import AddressDict


class JavaScriptContext(object):

def __init__(self, space):
self._ctx = lltype.nullptr(JSValueRef.TO)
self.space = space
self.w_js_exception = space.appexec([], '''():
class JSException(Exception):
pass
return JSException
''')
# note. It's safe to cast pointers in this dict to ints
# as they're non-movable (raw) ones
self.applevel_callbacks = {}
def callback(ctx, js_function, js_this, js_args):
arguments = Arguments(space,
[self.js_to_python(arg) for arg in js_args])
w_callable = self.applevel_callbacks.get(
rffi.cast(lltype.Signed, js_function), None)
if w_callable is None:
raise Exception("Got wrong callback, should not happen")
w_res = space.call_args(w_callable, arguments)
return self.python_to_js(w_res)
self.js_callback_factory = create_js_callback(callback)

def python_to_js(self, w_obj):
space = self.space
if space.is_w(w_obj, space.w_None):
return JSValueMakeUndefined(self._ctx)
elif space.is_true(space.isinstance(w_obj, space.w_bool)):
return JSValueMakeBoolean(self._ctx, space.is_true(w_obj))
elif space.is_true(space.isinstance(w_obj, space.w_int)):
return JSValueMakeNumber(self._ctx, space.int_w(w_obj))
elif space.is_true(space.isinstance(w_obj, space.w_float)):
return JSValueMakeNumber(self._ctx, space.float_w(w_obj))
elif space.is_true(space.isinstance(w_obj, space.w_str)):
return JSValueMakeString(self._ctx, self.newstr(space.str_w(w_obj)))
elif isinstance(w_obj, JSObject):
return w_obj.js_val
elif space.is_true(space.callable(w_obj)):
name = space.str_w(space.getattr(w_obj, space.wrap('__name__')))
js_func = self.js_callback_factory(self._ctx, name)
self.applevel_callbacks[rffi.cast(lltype.Signed, js_func)] = w_obj
return js_func
else:
raise NotImplementedError()

def js_to_python(self, js_obj, this=NULL):
space = self.space
tp = JSValueGetType(self._ctx, js_obj)
if tp == kJSTypeUndefined:
return space.w_None
elif tp == kJSTypeNull:
return space.w_None
elif tp == kJSTypeBoolean:
return space.wrap(JSValueToBoolean(self._ctx, js_obj))
elif tp == kJSTypeNumber:
return space.wrap(JSValueToNumber(self._ctx, js_obj))
elif tp == kJSTypeString:
return space.wrap(self.str_js(js_obj))
elif tp == kJSTypeObject:
return space.wrap(JSObject(self, js_obj, this))
else:
raise NotImplementedError(tp)

def newstr(self, s):
return JSStringCreateWithUTF8CString(s)

def str_js(self, js_s):
return JSStringGetUTF8CString(JSValueToString(self._ctx, js_s))

def get(self, js_obj, name):
return JSObjectGetProperty(self._ctx, js_obj, self.newstr(name))

def set(self, js_obj, name, js_val):
js_name = JSStringCreateWithUTF8CString(name)
JSObjectSetProperty(self._ctx, js_obj, js_name, js_val, 0)

def eval(self, s, this=NULL):
return JSEvaluateScript(self._ctx, s, this)

def call(self, js_val, args, this=NULL):
try:
return JSObjectCallAsFunction(self._ctx, js_val, this, args)
except JSException, e:
raise OperationError(self.w_js_exception, self.space.wrap(e.repr()))

def propertylist(self, js_val):
return JSPropertyList(self._ctx, js_val)

def globals(self):
return JSObject(self, JSContextGetGlobalObject(self._ctx))


class JSObject(Wrappable):

def __init__(self, ctx, js_val, this=NULL):
self.ctx = ctx
self.js_val = js_val
self.this = this

def descr_get(self, space, w_name):
name = space.str_w(w_name)
if name == '__dict__':
proplist = self.ctx.propertylist(self.js_val)
w_d = space.newdict()
for name in proplist:
w_item = self.ctx.js_to_python(self.ctx.get(self.js_val, name))
space.setitem(w_d, space.wrap(name), w_item)
return w_d
js_val = self.ctx.get(self.js_val, name)
return self.ctx.js_to_python(js_val, self.js_val)

def descr_set(self, space, w_name, w_value):
name = space.str_w(w_name)
if name == '__dict__':
raise OperationError(space.w_ValueError,
space.wrap("Cannot change __dict__"))
js_val = self.ctx.python_to_js(w_value)
self.ctx.set(self.js_val, name, js_val)
return space.w_None

def call(self, space, args_w):
js_res = self.ctx.call(self.js_val, [self.ctx.python_to_js(arg)
for arg in args_w], self.this)
return self.ctx.js_to_python(js_res)

def str(self, space):
return space.wrap('JSObject(' + self.ctx.str_js(self.js_val) + ')')

JSObject.typedef = TypeDef("JSObject",
__getattribute__ = interp2app(JSObject.descr_get,
unwrap_spec=['self', ObjSpace, W_Root]),
__getitem__ = interp2app(JSObject.descr_get,
unwrap_spec=['self', ObjSpace, W_Root]),
__setitem__ = interp2app(JSObject.descr_set,
unwrap_spec=['self', ObjSpace, W_Root, W_Root]),
__setattr__ = interp2app(JSObject.descr_set,
unwrap_spec=['self', ObjSpace, W_Root, W_Root]),
__str__ = interp2app(JSObject.str,
unwrap_spec=['self', ObjSpace]),
__call__ = interp2app(JSObject.call,
unwrap_spec=['self', ObjSpace, 'args_w'])
)
2 changes: 2 additions & 0 deletions Library/thepianpython/webkit_bridge/test/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# No Copyright (-) 2009-2010 The Ampify Authors. This file is under the
# Public Domain license that can be found in the root LICENSE file.
112 changes: 112 additions & 0 deletions Library/thepianpython/webkit_bridge/test/test_applevel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# No Copyright (-) 2009-2010 The Ampify Authors. This file is under the
# Public Domain license that can be found in the root LICENSE file.

from pypy.conftest import gettestobjspace
from webkit_bridge.jsobj import JSObject, JavaScriptContext
from webkit_bridge.webkit_rffi import (JSGlobalContextCreate,
JSGlobalContextRelease,
empty_object)
from pypy.interpreter.gateway import interp2app


class AppTestBindings(object):

def setup_class(cls):
ctx = JavaScriptContext(cls.space)
ctx._ctx = JSGlobalContextCreate()
cls.w_js_obj = cls.space.wrap(JSObject(ctx, ctx.eval('[]')))
this = ctx.eval('[]')
ctx.eval('this.x = function(a, b) { return(a + b); }', this)
cls.w_func = cls.space.wrap(JSObject(ctx, ctx.get(this, 'x')))
cls.w_js_obj_2 = cls.space.wrap(JSObject(ctx, ctx.eval('[]')))

def interpret(source):
return ctx.js_to_python(ctx.eval(source))

space = cls.space
cls.w_interpret = space.wrap(interp2app(interpret, unwrap_spec=[str]))
cls.w_globals = ctx.globals()
cls.w_JSException = ctx.w_js_exception

def test_getattr_none(self):
assert self.js_obj.x == None

def test_getsetattr_obj(self):
self.js_obj['x'] = 3
assert isinstance(self.js_obj.x, float)
assert self.js_obj.x == 3
self.js_obj.y = 3
assert isinstance(self.js_obj['y'], float)
assert self.js_obj['y'] == 3

def test_str(self):
assert str(self.js_obj) == 'JSObject()'

def test_call(self):
assert self.func(3, 4) == 7
assert self.func('a', 'bc') == 'abc'

def test_obj_wrap_unwrap(self):
self.js_obj['x'] = self.js_obj_2
assert str(self.js_obj.x) == 'JSObject()'

def test_floats(self):
self.js_obj.y = 3.5
assert self.js_obj.y == 3.5

def test_bools(self):
self.js_obj.x = True
assert self.js_obj.x

def test_none(self):
self.js_obj.x = None

def test_property_list(self):
x = self.interpret('''
function c () {
this.x = 1
this.y = 2
this.z = 3
}
new c()
''')
assert x.__dict__ == {'x':1, 'y':2, 'z':3}

def test_global(self):
self.interpret('''
xxx = 3
''')
assert self.globals.xxx == 3

def test_method(self):
x = self.interpret('''
function c () {
this.zzz = 3;
this.f = function (x) {
return (this.zzz + x);
};
}
new c()
''')
assert x.f(3) == 6

def test_raising_call(self):
f = self.interpret('''
function f(x) {
throw TypeError;
}
f
''')
raises(self.JSException, f, 3)

# XXX more raising tests

def test_wrapped_callback(self):
f = self.interpret('''
function f(x) {
return x(3);
}
f
''')
res = f(lambda x: x + 3)
assert res == 3 + 3
Loading

0 comments on commit ca13af9

Please sign in to comment.