JavaScript <-> Python <-> Lua intergation
=========================================

Lua and JavaScript are not connected directly; they communicate through Python.

Python <-> Lua is handled using lupa library.
:func:`splash.qtrender_lua.command` decorator handles most of Python <-> Lua
integration.

Python <-> JavaScript is handled using custom serialization code.
QT host objects are not used (with a few exceptions). Instead of this
JavaScript results are sanitized and processed in Python;
Python results are encoded to JSON and decoded/processed
in JavaScript.

Python -> Lua
-------------

Data is converted from Python to Lua in two cases:

1. method of an exposed Python object returns a result
   (most common example is a method of ``splash`` Lua object);
2. Python code calls Lua function with arguments - it could be e.g.
   an on_request callback.

Conversion rules:

* Basic Python types are converted to Lua: strings -> Lua strings,
  lists and dicts -> Lua tables, numbers -> Lua numbers, None -> nil(?).

  This is handled using :meth:`splash.lua_runtime.SplashLuaRuntime.python2lua`
  method. For attributes exposed to Lua this method is called manually;
  for return results of Python functions / methods it is handled by
  :func:`splash.qtrender_lua.emits_lua_objects` decorator. Methods decorated
  with ``@command`` use ``splash.qtrender_lua.emits_lua_objects`` internally,
  so a Python method decorated with ``@command`` decorator may return Python
  result in its body, and the final result would be a Lua object.

* If there is a need to expose a custom Python object to Lua then
  a subclass of :class:`splash.qtrender_lua.BaseExposedObject` is used; it is
  wrapped to a Lua table using utilities from wraputils.lua.
  Lua table exposes whitelisted attributes and methods of the object
  using metatable, and disallows access to all other attributes.

* Other than that, there is no automatic conversion. If something is not
  converted then it is available for Lua as an opaque userdata object;
  access to methods and attributes is disabled by a sandbox.

* To prevent wrapping method may return :class:`splash.lua.PyResult` instance.


Lua -> Python
-------------

Lua -> Python conversion is needed in two cases:

1. Lua code calls Python code, passing some arguments;
2. Python code calls Lua code and wants a result back.

* Basic Lua types are converted to Python using
  :meth:`splash.lua_runtime.SplashLuaRuntime.lua2python`. For method arguments
  lua2python is called by :func:`splash.qtrender_lua.decodes_lua_arguments`
  decorator; ``@command`` decorator uses ``decodes_lua_arguments`` internally.

* Python objects which were exposed to Lua (BaseExposedObject subclasses)
  are **not** converted back. By default they raise an error;
  with decode_arguments=False they are available as opaque
  Lua (lupa) table objects.

  :func:`splash.qtrender_lua.is_wrapped_exposed_object` can be used to check
  if a lupa object is a wrapped BaseExposedObject instance; obj.unwrapped()
  method can be used to access the underlying Python object.


JavaScript -> Python
--------------------

To get results from JavaScript to Python they are converted to primitive
JSON-serializable types first. QtWebKit host objects are not used.
Objects of unknown JavaScript types are discared, max depth of result
is limited.

JavaScript -> Python conversion utilities reside in

* :mod:`splash.jsutils` module - JavaScript side, i.e. sanitizing and encoding;
  two main functions are ``SANITIZE_FUNC_JS`` and ``STORE_DOM_ELEMENTS_JS``;
* :meth:`splash.browser_tab.BrowserTab.evaljs` method - Python side,
  i.e. decoding of the result.

For most types (objects, arrays, numbers, strings) conversion method
is straightforward; the most tricky case is a reference to DOM nodes.

For top-level DOM nodes (i.e. a result is a DOM node or a NodeList)
a node is stored in a special window attribute, and generated id is returned
to Python instead. All other DOM nodes are discarded - returning a Node
or a NodeList as a part of data structure is not supported at the moment.
``STORE_DOM_ELEMENTS_JS`` processes Node and NodeList objects;
``SANITIZE_FUNC_JS`` sanitizes the result (handles all other data types,
drops unsupported data).

In Python HTMLElement objects are created for DOM nodes; they contain node_id
attribute with id returned by JavaScript; it allows to fetch the real Node
object in JavaScript. This is handled by
:meth:`splash.browser_tab.BrowserTab.evaljs`.

Python -> JavaScript
--------------------

There are two cases Python objects are converted to JavaScript objects:

1. functions created with splash:jsfunc() are called with arguments;
2. methods of HtmlElement which wrap JS functions are called with arguments.

The conversion is handled either by :func:`splash.html_element.escape_js_args`
or by :func:`splash.jsutils.escape_js`.

* ``escape_js`` just encodes Python data to JSON and removes quotes; the result
  can be used as literal representation of argument values, i.e. added to
  a JS function call using string formatting.
* ``escape_js_args`` is similar to ``escape_js``, but it handles
  ``splash.html_element.HTMLElement`` instances by replacing them with JS
  code to access stored nodes.