import spidermonkey from xml.dom import minidom import os import sys import traceback import py import dom here = py.magic.autopath().dirpath() # exception definition, raised when there's a problem evaluating the JS, in # which case the JSPyError below is used as a container for the exception data class PyJSError(Exception): def __init__(self, name, message, stack): self.args = (name, message, stack) self.name = name self.message = message self.stack = stack class JSPyError: def __init__(self, name, message, stack): self.args = (name, message, stack) self.name = name self.message = message self.stack = stack.split('\n') def get_exception(): exc, e, tb = sys.exc_info() ret = JSPyError(str(exc), str(e), ''.join(traceback.format_tb(tb))) del tb return ret # objects which are passed to the JS context - note that extra care must be # taken to catch exceptions, as an exception raised here results in the script # being stopped entirely, without raising a JS exception class window: navigator = { 'appName': 'Netscape', 'appVersion': '5.0 (X11; en-US)', 'appCodeName': 'Mozilla', 'platform': 'Python', } events = [] def __init__(self, browser, html): self._browser = browser self._doc = doc = minidom.parseString(html) self.document = dom.NodeWrapper(self, doc) titleels = doc.getElementsByTagName('title') if titleels: self.title = dom.text_from_node(titleels[0]) else: self.title = title scripttags = doc.getElementsByTagName('script') for st in scripttags: if st.hasAttribute('src'): browser.load(st.getAttribute('src')) else: content = dom.text_from_node(st) browser.eval(context) def printline(self, *args): msg = [] for a in args: if isinstance(a, unicode): a = a.encode('UTF-8') else: a = str(a) print a, class document: def __init__(self, window, doc): self._window = window self._doc = dom.NodeWrapper(self, doc) def __getattr__(self, name): return getattr(self._doc, name) class fakebrowser: def __init__(self, webrootpath, webpath): self.webrootpath = webrootpath self.webpath = webpath self.runtime = spidermonkey.Runtime() self.context = self.runtime.new_context() self.context.bind_class(fakebrowser, bind_constructor=False) self.context.bind_class(JSPyError, bind_constructor=True) self.context.bind_object('__fakebrowser__', self) def initwindow(self, html=None): self.currpath = cpath = self.webpath.split('/') self.htmlfile = hfile = cpath.pop() if not hfile: self.htmlfile = hfile = 'index.html' if not html: try: html = self.webrootpath.join(*cpath).join(hfile).read() except py.error.ENOENT: html = '' self.context.bind_class(window, bind_constructor=False) self.context.bind_class(document, bind_constructor=False) self.context.bind_class(dom.NodeWrapper, bind_constructor=True) self.context.bind_class(dom.DOMFunctionWrapper, bind_constructor=True) self.window = w = window(self, html) self.context.bind_object('__window', w) self.context.bind_object('__document', w.document) self.eval(here.join('fakebrowser.js').read()) def load(self, path): """ load and evaluate a JS file path is a web-path, either relative from the 'current position' (the web page's parent in case this is called directly, the JS file's parent in case it's called from a JS file using load()) or absolute from the web root """ items = path.split('/') if items[0] != '': items = self.currpath + items abspath = self.webrootpath.join(os.path.sep.join(items)) old_currpath = self.currpath self.currpath = abspath.dirpath().strpath.split('/') print 'reading file', path, ', currpath', '/'.join(self.currpath) self.eval(abspath.read()) self.currpath = old_currpath def eval(self, js): s = (''' try { eval(%r); } catch(e) { var s = e.toString(); if (s.indexOf(': ') > -1) { var namemsg = e.toString().split(': '); new JSPyError(namemsg[0], namemsg[1], e.stack || ''); }; }; ''' % (js,)) ret = self.context.eval_script(s) if isinstance(ret, JSPyError): raise PyJSError(ret.name, ret.message, ret.stack) return ret