Bug 1313295 - Merge mortar into mozilla-central. r=peterv
authorlochang <ghoo1125@yahoo.com.tw>
Mon, 12 Dec 2016 19:30:00 -0500
changeset 918237 5956d0c8b4f054352ae88ecee7fb4fc7708f5959
parent 918236 6768b6d8ba5a90cbd21b5d3bc03b0796d3509c7f
child 918238 4903bfce17c6cf80f082a27e95e90598726c6c5e
push id160282
push usermcmanus@ducksong.com
push dateTue, 20 Dec 2016 20:43:39 +0000
treeherdertry@4772b4f39a2b [default view] [failures only]
reviewerspeterv
bugs1313295
milestone53.0a1
Bug 1313295 - Merge mortar into mozilla-central. r=peterv
browser/extensions/mortar/Makefile
browser/extensions/mortar/host/common/opengles2-utils.jsm
browser/extensions/mortar/host/common/ppapi-instance.js
browser/extensions/mortar/host/common/ppapi-runtime.jsm
browser/extensions/mortar/host/flash/bootstrap.js
browser/extensions/mortar/host/flash/chrome.manifest
browser/extensions/mortar/host/flash/chrome/viewer.html
browser/extensions/mortar/host/flash/install.rdf
browser/extensions/mortar/host/flash/ppapi-content-sandbox.js
browser/extensions/mortar/host/interpose.cc
browser/extensions/mortar/host/pdf/bootstrap.js
browser/extensions/mortar/host/pdf/chrome.manifest
browser/extensions/mortar/host/pdf/chrome/js/l20n.js
browser/extensions/mortar/host/pdf/chrome/js/polyfill.js
browser/extensions/mortar/host/pdf/chrome/js/toolbar.js
browser/extensions/mortar/host/pdf/chrome/js/viewer.js
browser/extensions/mortar/host/pdf/chrome/js/viewport.js
browser/extensions/mortar/host/pdf/chrome/locale/viewer.en-US.properties
browser/extensions/mortar/host/pdf/chrome/style/images/annotation-check.svg
browser/extensions/mortar/host/pdf/chrome/style/images/annotation-comment.svg
browser/extensions/mortar/host/pdf/chrome/style/images/annotation-help.svg
browser/extensions/mortar/host/pdf/chrome/style/images/annotation-insert.svg
browser/extensions/mortar/host/pdf/chrome/style/images/annotation-key.svg
browser/extensions/mortar/host/pdf/chrome/style/images/annotation-newparagraph.svg
browser/extensions/mortar/host/pdf/chrome/style/images/annotation-noicon.svg
browser/extensions/mortar/host/pdf/chrome/style/images/annotation-note.svg
browser/extensions/mortar/host/pdf/chrome/style/images/annotation-paragraph.svg
browser/extensions/mortar/host/pdf/chrome/style/images/findbarButton-next-rtl.png
browser/extensions/mortar/host/pdf/chrome/style/images/findbarButton-next-rtl@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/findbarButton-next.png
browser/extensions/mortar/host/pdf/chrome/style/images/findbarButton-next@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/findbarButton-previous-rtl.png
browser/extensions/mortar/host/pdf/chrome/style/images/findbarButton-previous-rtl@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/findbarButton-previous.png
browser/extensions/mortar/host/pdf/chrome/style/images/findbarButton-previous@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/grab.cur
browser/extensions/mortar/host/pdf/chrome/style/images/grabbing.cur
browser/extensions/mortar/host/pdf/chrome/style/images/loading-icon.gif
browser/extensions/mortar/host/pdf/chrome/style/images/loading-small.png
browser/extensions/mortar/host/pdf/chrome/style/images/loading-small@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-documentProperties.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-documentProperties@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-firstPage.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-firstPage@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-handTool.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-handTool@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-lastPage.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-lastPage@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-rotateCcw.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-rotateCcw@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-rotateCw.png
browser/extensions/mortar/host/pdf/chrome/style/images/secondaryToolbarButton-rotateCw@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/shadow.png
browser/extensions/mortar/host/pdf/chrome/style/images/texture.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-bookmark.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-bookmark@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-download.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-download@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-menuArrows.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-menuArrows@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-openFile.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-openFile@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-pageDown-rtl.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-pageDown-rtl@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-pageDown.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-pageDown@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-pageUp-rtl.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-pageUp-rtl@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-pageUp.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-pageUp@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-presentationMode.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-presentationMode@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-print.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-print@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-search.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-search@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-secondaryToolbarToggle-rtl.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-secondaryToolbarToggle.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-secondaryToolbarToggle@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-sidebarToggle-rtl.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-sidebarToggle-rtl@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-sidebarToggle.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-sidebarToggle@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-viewAttachments.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-viewAttachments@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-viewOutline-rtl.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-viewOutline-rtl@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-viewOutline.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-viewOutline@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-viewThumbnail.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-viewThumbnail@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-zoomIn.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-zoomIn@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-zoomOut.png
browser/extensions/mortar/host/pdf/chrome/style/images/toolbarButton-zoomOut@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/treeitem-collapsed-rtl.png
browser/extensions/mortar/host/pdf/chrome/style/images/treeitem-collapsed-rtl@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/treeitem-collapsed.png
browser/extensions/mortar/host/pdf/chrome/style/images/treeitem-collapsed@2x.png
browser/extensions/mortar/host/pdf/chrome/style/images/treeitem-expanded.png
browser/extensions/mortar/host/pdf/chrome/style/images/treeitem-expanded@2x.png
browser/extensions/mortar/host/pdf/chrome/style/viewer.css
browser/extensions/mortar/host/pdf/chrome/viewer.html
browser/extensions/mortar/host/pdf/install.rdf
browser/extensions/mortar/host/pdf/ppapi-content-sandbox.js
browser/extensions/mortar/host/rpc.cc
browser/extensions/mortar/host/rpc.h
browser/extensions/mortar/json/json.cpp
browser/extensions/mortar/json/json.h
browser/extensions/mortar/json/test.cpp
browser/extensions/mortar/ppapi/LICENSE
browser/extensions/mortar/ppapi/api/dev/pp_cursor_type_dev.idl
browser/extensions/mortar/ppapi/api/dev/pp_print_settings_dev.idl
browser/extensions/mortar/ppapi/api/dev/pp_video_capture_dev.idl
browser/extensions/mortar/ppapi/api/dev/pp_video_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_audio_input_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_buffer_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_char_set_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_crypto_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_cursor_control_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_device_ref_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_file_chooser_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_font_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_ime_input_event_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_memory_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_opengles2ext_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_printing_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_text_input_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_trace_event_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_truetype_font_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_url_util_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_var_deprecated.idl
browser/extensions/mortar/ppapi/api/dev/ppb_video_capture_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_video_decoder_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppb_view_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppp_class_deprecated.idl
browser/extensions/mortar/ppapi/api/dev/ppp_network_state_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppp_printing_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppp_text_input_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppp_video_capture_dev.idl
browser/extensions/mortar/ppapi/api/dev/ppp_video_decoder_dev.idl
browser/extensions/mortar/ppapi/api/pp_array_output.idl
browser/extensions/mortar/ppapi/api/pp_bool.idl
browser/extensions/mortar/ppapi/api/pp_codecs.idl
browser/extensions/mortar/ppapi/api/pp_completion_callback.idl
browser/extensions/mortar/ppapi/api/pp_directory_entry.idl
browser/extensions/mortar/ppapi/api/pp_errors.idl
browser/extensions/mortar/ppapi/api/pp_file_info.idl
browser/extensions/mortar/ppapi/api/pp_graphics_3d.idl
browser/extensions/mortar/ppapi/api/pp_input_event.idl
browser/extensions/mortar/ppapi/api/pp_instance.idl
browser/extensions/mortar/ppapi/api/pp_macros.idl
browser/extensions/mortar/ppapi/api/pp_module.idl
browser/extensions/mortar/ppapi/api/pp_point.idl
browser/extensions/mortar/ppapi/api/pp_rect.idl
browser/extensions/mortar/ppapi/api/pp_resource.idl
browser/extensions/mortar/ppapi/api/pp_size.idl
browser/extensions/mortar/ppapi/api/pp_stdint.idl
browser/extensions/mortar/ppapi/api/pp_time.idl
browser/extensions/mortar/ppapi/api/pp_touch_point.idl
browser/extensions/mortar/ppapi/api/pp_var.idl
browser/extensions/mortar/ppapi/api/ppb.idl
browser/extensions/mortar/ppapi/api/ppb_audio.idl
browser/extensions/mortar/ppapi/api/ppb_audio_buffer.idl
browser/extensions/mortar/ppapi/api/ppb_audio_config.idl
browser/extensions/mortar/ppapi/api/ppb_audio_encoder.idl
browser/extensions/mortar/ppapi/api/ppb_compositor.idl
browser/extensions/mortar/ppapi/api/ppb_compositor_layer.idl
browser/extensions/mortar/ppapi/api/ppb_console.idl
browser/extensions/mortar/ppapi/api/ppb_core.idl
browser/extensions/mortar/ppapi/api/ppb_file_io.idl
browser/extensions/mortar/ppapi/api/ppb_file_ref.idl
browser/extensions/mortar/ppapi/api/ppb_file_system.idl
browser/extensions/mortar/ppapi/api/ppb_fullscreen.idl
browser/extensions/mortar/ppapi/api/ppb_gamepad.idl
browser/extensions/mortar/ppapi/api/ppb_graphics_2d.idl
browser/extensions/mortar/ppapi/api/ppb_graphics_3d.idl
browser/extensions/mortar/ppapi/api/ppb_host_resolver.idl
browser/extensions/mortar/ppapi/api/ppb_image_data.idl
browser/extensions/mortar/ppapi/api/ppb_input_event.idl
browser/extensions/mortar/ppapi/api/ppb_instance.idl
browser/extensions/mortar/ppapi/api/ppb_media_stream_audio_track.idl
browser/extensions/mortar/ppapi/api/ppb_media_stream_video_track.idl
browser/extensions/mortar/ppapi/api/ppb_message_loop.idl
browser/extensions/mortar/ppapi/api/ppb_messaging.idl
browser/extensions/mortar/ppapi/api/ppb_mouse_cursor.idl
browser/extensions/mortar/ppapi/api/ppb_mouse_lock.idl
browser/extensions/mortar/ppapi/api/ppb_net_address.idl
browser/extensions/mortar/ppapi/api/ppb_network_list.idl
browser/extensions/mortar/ppapi/api/ppb_network_monitor.idl
browser/extensions/mortar/ppapi/api/ppb_network_proxy.idl
browser/extensions/mortar/ppapi/api/ppb_opengles2.idl
browser/extensions/mortar/ppapi/api/ppb_tcp_socket.idl
browser/extensions/mortar/ppapi/api/ppb_text_input_controller.idl
browser/extensions/mortar/ppapi/api/ppb_udp_socket.idl
browser/extensions/mortar/ppapi/api/ppb_url_loader.idl
browser/extensions/mortar/ppapi/api/ppb_url_request_info.idl
browser/extensions/mortar/ppapi/api/ppb_url_response_info.idl
browser/extensions/mortar/ppapi/api/ppb_var.idl
browser/extensions/mortar/ppapi/api/ppb_var_array.idl
browser/extensions/mortar/ppapi/api/ppb_var_array_buffer.idl
browser/extensions/mortar/ppapi/api/ppb_var_dictionary.idl
browser/extensions/mortar/ppapi/api/ppb_video_decoder.idl
browser/extensions/mortar/ppapi/api/ppb_video_encoder.idl
browser/extensions/mortar/ppapi/api/ppb_video_frame.idl
browser/extensions/mortar/ppapi/api/ppb_view.idl
browser/extensions/mortar/ppapi/api/ppb_vpn_provider.idl
browser/extensions/mortar/ppapi/api/ppb_websocket.idl
browser/extensions/mortar/ppapi/api/ppp.idl
browser/extensions/mortar/ppapi/api/ppp_graphics_3d.idl
browser/extensions/mortar/ppapi/api/ppp_input_event.idl
browser/extensions/mortar/ppapi/api/ppp_instance.idl
browser/extensions/mortar/ppapi/api/ppp_message_handler.idl
browser/extensions/mortar/ppapi/api/ppp_messaging.idl
browser/extensions/mortar/ppapi/api/ppp_mouse_lock.idl
browser/extensions/mortar/ppapi/api/private/finish_writing_these/ppb_flash_file.idl
browser/extensions/mortar/ppapi/api/private/finish_writing_these/ppb_pdf.idl
browser/extensions/mortar/ppapi/api/private/finish_writing_these/ppb_proxy_private.idl
browser/extensions/mortar/ppapi/api/private/pp_content_decryptor.idl
browser/extensions/mortar/ppapi/api/private/pp_file_handle.idl
browser/extensions/mortar/ppapi/api/private/pp_private_font_charset.idl
browser/extensions/mortar/ppapi/api/private/pp_video_capture_format.idl
browser/extensions/mortar/ppapi/api/private/pp_video_frame_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_camera_capabilities_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_camera_device_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_content_decryptor_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_display_color_profile_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_ext_crx_file_system_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_file_io_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_file_ref_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_find_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_flash.idl
browser/extensions/mortar/ppapi/api/private/ppb_flash_clipboard.idl
browser/extensions/mortar/ppapi/api/private/ppb_flash_device_id.idl
browser/extensions/mortar/ppapi/api/private/ppb_flash_drm.idl
browser/extensions/mortar/ppapi/api/private/ppb_flash_file.idl
browser/extensions/mortar/ppapi/api/private/ppb_flash_font_file.idl
browser/extensions/mortar/ppapi/api/private/ppb_flash_fullscreen.idl
browser/extensions/mortar/ppapi/api/private/ppb_flash_menu.idl
browser/extensions/mortar/ppapi/api/private/ppb_flash_message_loop.idl
browser/extensions/mortar/ppapi/api/private/ppb_flash_print.idl
browser/extensions/mortar/ppapi/api/private/ppb_host_resolver_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_instance_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_isolated_file_system_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_net_address_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_output_protection_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_pdf.idl
browser/extensions/mortar/ppapi/api/private/ppb_platform_verification_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_tcp_server_socket_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_tcp_socket_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_testing_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_udp_socket_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_uma_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_video_destination_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_video_source_private.idl
browser/extensions/mortar/ppapi/api/private/ppb_x509_certificate_private.idl
browser/extensions/mortar/ppapi/api/private/ppp_content_decryptor_private.idl
browser/extensions/mortar/ppapi/api/private/ppp_find_private.idl
browser/extensions/mortar/ppapi/api/private/ppp_flash_browser_operations.idl
browser/extensions/mortar/ppapi/api/private/ppp_instance_private.idl
browser/extensions/mortar/ppapi/api/private/ppp_pexe_stream_handler.idl
browser/extensions/mortar/ppapi/api/trusted/ppb_broker_trusted.idl
browser/extensions/mortar/ppapi/api/trusted/ppb_browser_font_trusted.idl
browser/extensions/mortar/ppapi/api/trusted/ppb_char_set_trusted.idl
browser/extensions/mortar/ppapi/api/trusted/ppb_file_chooser_trusted.idl
browser/extensions/mortar/ppapi/api/trusted/ppb_url_loader_trusted.idl
browser/extensions/mortar/ppapi/api/trusted/ppp_broker.idl
browser/extensions/mortar/ppapi/c/pp_graphics_3d.h
browser/extensions/mortar/ppapi/c/pp_instance.h
browser/extensions/mortar/ppapi/c/pp_module.h
browser/extensions/mortar/ppapi/c/pp_resource.h
browser/extensions/mortar/ppapi/c/pp_stdint.h
browser/extensions/mortar/ppapi/c/ppb.h
browser/extensions/mortar/ppapi/c/ppb_opengles2.h
browser/extensions/mortar/ppapi/c/private/pp_file_handle.h
browser/extensions/mortar/ppapi/generators/OWNERS
browser/extensions/mortar/ppapi/generators/generator.py
browser/extensions/mortar/ppapi/generators/idl_ast.py
browser/extensions/mortar/ppapi/generators/idl_c_header.py
browser/extensions/mortar/ppapi/generators/idl_c_proto.py
browser/extensions/mortar/ppapi/generators/idl_diff.py
browser/extensions/mortar/ppapi/generators/idl_gen_pnacl.py
browser/extensions/mortar/ppapi/generators/idl_gen_rpc.py
browser/extensions/mortar/ppapi/generators/idl_gen_wrapper.py
browser/extensions/mortar/ppapi/generators/idl_generator.py
browser/extensions/mortar/ppapi/generators/idl_lexer.py
browser/extensions/mortar/ppapi/generators/idl_lint.py
browser/extensions/mortar/ppapi/generators/idl_log.py
browser/extensions/mortar/ppapi/generators/idl_namespace.py
browser/extensions/mortar/ppapi/generators/idl_node.py
browser/extensions/mortar/ppapi/generators/idl_option.py
browser/extensions/mortar/ppapi/generators/idl_outfile.py
browser/extensions/mortar/ppapi/generators/idl_parser.py
browser/extensions/mortar/ppapi/generators/idl_propertynode.py
browser/extensions/mortar/ppapi/generators/idl_release.py
browser/extensions/mortar/ppapi/generators/idl_tests.py
browser/extensions/mortar/ppapi/generators/idl_thunk.py
browser/extensions/mortar/ppapi/generators/idl_visitor.py
browser/extensions/mortar/ppapi/generators/test_cgen/enum_typedef.h
browser/extensions/mortar/ppapi/generators/test_cgen/enum_typedef.idl
browser/extensions/mortar/ppapi/generators/test_cgen/interface.h
browser/extensions/mortar/ppapi/generators/test_cgen/interface.idl
browser/extensions/mortar/ppapi/generators/test_cgen/stdint.h
browser/extensions/mortar/ppapi/generators/test_cgen/stdint.idl
browser/extensions/mortar/ppapi/generators/test_cgen/structs.h
browser/extensions/mortar/ppapi/generators/test_cgen/structs.idl
browser/extensions/mortar/ppapi/generators/test_cgen_range/dev_channel_interface.h
browser/extensions/mortar/ppapi/generators/test_cgen_range/dev_channel_interface.idl
browser/extensions/mortar/ppapi/generators/test_cgen_range/versions.h
browser/extensions/mortar/ppapi/generators/test_cgen_range/versions.idl
browser/extensions/mortar/ppapi/generators/test_gen_pnacl/test_interfaces.idl
browser/extensions/mortar/ppapi/generators/test_namespace/bar.idl
browser/extensions/mortar/ppapi/generators/test_namespace/foo.idl
browser/extensions/mortar/ppapi/generators/test_parser/dictionary.idl
browser/extensions/mortar/ppapi/generators/test_parser/enum.idl
browser/extensions/mortar/ppapi/generators/test_parser/interface.idl
browser/extensions/mortar/ppapi/generators/test_parser/struct.idl
browser/extensions/mortar/ppapi/generators/test_parser/typedef.idl
browser/extensions/mortar/ppapi/generators/test_thunk/basic_test_types.idl
browser/extensions/mortar/ppapi/generators/test_thunk/simple.idl
browser/extensions/mortar/ppapi/generators/test_thunk/simple_thunk.cc
browser/extensions/mortar/ppapi/generators/test_version/versions.idl
browser/extensions/mortar/ppapi/out/rpc.cc
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/Makefile
@@ -0,0 +1,54 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# We keep the Makefile until the mortar is integrated into gecko build system.
+ifeq ($(shell uname), Darwin)
+	DLL_SUFFIX = .dylib
+	CXX = g++
+	CXX_FLAGS = -std=c++11 -g -fPIC
+	LD_FLAGS = -dynamiclib
+ifdef DEBUG
+	CXX_FLAGS += -DDEBUG
+endif
+else ifeq ($(shell uname), Linux)
+	DLL_SUFFIX = .so
+	CXX = g++
+	CXX_FLAGS = -std=c++11 -g -fPIC
+	LD_FLAGS = -shared
+ifdef DEBUG
+	CXX_FLAGS += -DDEBUG
+endif
+else
+	DLL_SUFFIX = .dll
+	CXX = cl
+	CXX_FLAGS = -nologo -EHsc -Oy-
+ifdef DEBUG
+	CXX_FLAGS += -LDd -DDEBUG -Od
+else
+	CXX_FLAGS += -LD
+endif
+	LD_FLAGS = -link -dll
+endif
+
+all : ppapi/out/rpc$(DLL_SUFFIX) ppapi/out/interpose$(DLL_SUFFIX)
+
+ppapi/out/rpc$(DLL_SUFFIX): ppapi/out/rpc.cc host/rpc.h host/rpc.cc
+	$(CXX) $(CXX_FLAGS) -I. -o $@ host/rpc.cc $(LD_FLAGS)
+
+ppapi/out/interpose$(DLL_SUFFIX): ppapi/out/rpc.cc host/rpc.h host/interpose.cc
+	$(CXX) -DINTERPOSE $(CXX_FLAGS) -I. -o $@ host/interpose.cc $(LD_FLAGS)
+
+ppapi/out/rpc.cc: $(shell find . -name *.idl) $(shell find . -name *.py)
+	cd ppapi/generators; python idl_gen_rpc.py --out ../out/rpc.cc ; cd ../..
+
+json/test: json/json.cpp json/json.h json/test.cpp
+	$(CXX) -I./json -o $@ json/test.cpp
+
+test-json: json/test
+	@./json/test && echo "OK"
+
+clean:
+	rm -rf ppapi/generators/*.pyc ppapi/generators/*~ *~ ppapi/out/* json/*~ json/test *.obj
+
+.PHONY: all test-json test clean
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/common/opengles2-utils.jsm
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+let GLES2Utils = {
+  bytesPerElement: function(context, type) {
+    switch (type) {
+      case context.FLOAT:
+      case context.INT:
+      case context.UNSIGNED_INT:
+        return 4;
+      case context.SHORT:
+      case context.UNSIGNED_SHORT:
+      case context.UNSIGNED_SHORT_5_6_5:
+      case context.UNSIGNED_SHORT_4_4_4_4:
+      case context.UNSIGNED_SHORT_5_5_5_1:
+        return 2;
+      case context.BYTE:
+      case context.UNSIGNED_BYTE:
+        return 1;
+      default:
+        throw new Error("Don't know this type.");
+    }
+  },
+  elementsPerGroup: function(context, format, type) {
+    switch (type) {
+      case context.UNSIGNED_SHORT_5_6_5:
+      case context.UNSIGNED_SHORT_4_4_4_4:
+      case context.UNSIGNED_SHORT_5_5_5_1:
+        return 1;
+      default:
+        break;
+    }
+
+    switch (format) {
+      case context.RGB:
+        return 3;
+      case context.LUMINANCE_ALPHA:
+        return 2;
+      case context.RGBA:
+        return 4;
+      case context.ALPHA:
+      case context.LUMINANCE:
+      case context.DEPTH_COMPONENT:
+      case context.DEPTH_COMPONENT16:
+        return 1;
+      default:
+        throw new Error("Don't know this format.");
+    }
+  },
+  computeImageGroupSize: function(context, format, type) {
+    return this.bytesPerElement(context, type) * this.elementsPerGroup(context, format, type);
+  },
+  computeImageDataSize: function(context, width, height, format, type) {
+    const unpackAlignment = 4;
+
+    let bytesPerGroup = this.computeImageGroupSize(context, format, type);
+    let rowSize = width * bytesPerGroup;
+    if (height == 1) {
+      return rowSize;
+    }
+
+    let temp = rowSize + unpackAlignment - 1;
+    let paddedRowSize = Math.floor(temp / unpackAlignment) * unpackAlignment;
+    let sizeOfAllButLastRow = (height - 1) * paddedRowSize;
+    return sizeOfAllButLastRow + rowSize;
+  },
+};
+
+var EXPORTED_SYMBOLS = ["GLES2Utils"];
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/common/ppapi-instance.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+let rt;
+function getRuntime(type) {
+  if (!rt) {
+    let process = Cc["@mozilla.org/plugin/ppapi.js-process;1"].getService(Ci.nsIPPAPIJSProcess);
+    Cu.import("resource://ppapi.js/ppapi-runtime.jsm");
+    rt = new PPAPIRuntime(process);
+    process.launch(rt.callback);
+  }
+  return rt;
+}
+
+addMessageListener("ppapi.js:createInstance", ({ target, data: { type, info }, objects: { pluginWindow } }) => {
+  dump("ppapi.js:createInstance\n");
+  let rt = getRuntime(type);
+  let instance = rt.createInstance(info, content, docShell.chromeEventHandler, pluginWindow, target);
+  addEventListener("unload", () => {
+    rt.destroyInstance(instance);
+  });
+});
+
+addEventListener("DOMContentLoaded", () => {
+  // Passing an object here forces the creation of the CPOW manager in the
+  // parent.
+  sendRpcMessage("ppapi.js:frameLoaded", undefined, {});
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/common/ppapi-runtime.jsm
@@ -0,0 +1,5308 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://ppapi.js/opengles2-utils.jsm");
+
+const PP_OK = 0;
+const PP_OK_COMPLETIONPENDING = -1;
+const PP_ERROR_FAILED = -2;
+const PP_ERROR_ABORTED = -3;
+const PP_ERROR_BADARGUMENT = -4;
+const PP_ERROR_BADRESOURCE = -5;
+const PP_ERROR_NOINTERFACE = -6;
+const PP_ERROR_NOACCESS = -7;
+const PP_ERROR_NOMEMORY = -8;
+const PP_ERROR_NOSPACE = -9;
+const PP_ERROR_NOQUOTA = -10;
+const PP_ERROR_INPROGRESS = -11;
+const PP_ERROR_NOTSUPPORTED = -12;
+const PP_ERROR_BLOCKS_MAIN_THREAD = -13;
+const PP_ERROR_MALFORMED_INPUT = -14;
+const PP_ERROR_RESOURCE_FAILED = -15;
+const PP_ERROR_FILENOTFOUND = -20;
+const PP_ERROR_FILEEXISTS = -21;
+const PP_ERROR_FILETOOBIG = -22;
+const PP_ERROR_FILECHANGED = -23;
+const PP_ERROR_NOTAFILE = -24;
+const PP_ERROR_TIMEDOUT = -30;
+const PP_ERROR_USERCANCEL = -40;
+const PP_ERROR_NO_USER_GESTURE = -41;
+const PP_ERROR_CONTEXT_LOST = -50;
+const PP_ERROR_NO_MESSAGE_LOOP = -51;
+const PP_ERROR_WRONG_THREAD = -52;
+const PP_ERROR_WOULD_BLOCK_THREAD = -53;
+const PP_ERROR_CONNECTION_CLOSED = -100;
+const PP_ERROR_CONNECTION_RESET = -101;
+const PP_ERROR_CONNECTION_REFUSED = -102;
+const PP_ERROR_CONNECTION_ABORTED = -103;
+const PP_ERROR_CONNECTION_FAILED = -104;
+const PP_ERROR_CONNECTION_TIMEDOUT = -105;
+const PP_ERROR_ADDRESS_INVALID = -106;
+const PP_ERROR_ADDRESS_UNREACHABLE = -107;
+const PP_ERROR_ADDRESS_IN_USE = -108;
+const PP_ERROR_MESSAGE_TOO_BIG = -109;
+const PP_ERROR_NAME_NOT_RESOLVED = -110;
+
+
+const PP_Bool = {
+  PP_FALSE: 0,
+  PP_TRUE: 1,
+};
+
+const PP_AudioFrameSize = {
+  PP_AUDIOMINSAMPLEFRAMECOUNT: 64,
+  PP_AUDIOMAXSAMPLEFRAMECOUNT: 32768,
+};
+
+const PP_BrowserFont_Trusted_Family = {
+  PP_BROWSERFONT_TRUSTED_FAMILY_DEFAULT: 0,
+  PP_BROWSERFONT_TRUSTED_FAMILY_SERIF: 1,
+  PP_BROWSERFONT_TRUSTED_FAMILY_SANSSERIF: 2,
+  PP_BROWSERFONT_TRUSTED_FAMILY_MONOSPACE: 3,
+};
+
+const PP_BrowserFont_Trusted_Weight = {
+  PP_BROWSERFONT_TRUSTED_WEIGHT_100: 0,
+  PP_BROWSERFONT_TRUSTED_WEIGHT_200: 1,
+  PP_BROWSERFONT_TRUSTED_WEIGHT_300: 2,
+  PP_BROWSERFONT_TRUSTED_WEIGHT_400: 3,
+  PP_BROWSERFONT_TRUSTED_WEIGHT_500: 4,
+  PP_BROWSERFONT_TRUSTED_WEIGHT_600: 5,
+  PP_BROWSERFONT_TRUSTED_WEIGHT_700: 6,
+  PP_BROWSERFONT_TRUSTED_WEIGHT_800: 7,
+  PP_BROWSERFONT_TRUSTED_WEIGHT_900: 8,
+  PP_BROWSERFONT_TRUSTED_WEIGHT_NORMAL: 3,
+  PP_BROWSERFONT_TRUSTED_WEIGHT_BOLD: 6,
+};
+
+const PP_CursorType_Dev = {
+  PP_CURSORTYPE_CUSTOM: -1,
+  PP_CURSORTYPE_POINTER: 0,
+  PP_CURSORTYPE_CROSS: 1,
+  PP_CURSORTYPE_HAND: 2,
+  PP_CURSORTYPE_IBEAM: 3,
+  PP_CURSORTYPE_WAIT: 4,
+  PP_CURSORTYPE_HELP: 5,
+  PP_CURSORTYPE_EASTRESIZE: 6,
+  PP_CURSORTYPE_NORTHRESIZE: 7,
+  PP_CURSORTYPE_NORTHEASTRESIZE: 8,
+  PP_CURSORTYPE_NORTHWESTRESIZE: 9,
+  PP_CURSORTYPE_SOUTHRESIZE: 10,
+  PP_CURSORTYPE_SOUTHEASTRESIZE: 11,
+  PP_CURSORTYPE_SOUTHWESTRESIZE: 12,
+  PP_CURSORTYPE_WESTRESIZE: 13,
+  PP_CURSORTYPE_NORTHSOUTHRESIZE: 14,
+  PP_CURSORTYPE_EASTWESTRESIZE: 15,
+  PP_CURSORTYPE_NORTHEASTSOUTHWESTRESIZE: 16,
+  PP_CURSORTYPE_NORTHWESTSOUTHEASTRESIZE: 17,
+  PP_CURSORTYPE_COLUMNRESIZE: 18,
+  PP_CURSORTYPE_ROWRESIZE: 19,
+  PP_CURSORTYPE_MIDDLEPANNING: 20,
+  PP_CURSORTYPE_EASTPANNING: 21,
+  PP_CURSORTYPE_NORTHPANNING: 22,
+  PP_CURSORTYPE_NORTHEASTPANNING: 23,
+  PP_CURSORTYPE_NORTHWESTPANNING: 24,
+  PP_CURSORTYPE_SOUTHPANNING: 25,
+  PP_CURSORTYPE_SOUTHEASTPANNING: 26,
+  PP_CURSORTYPE_SOUTHWESTPANNING: 27,
+  PP_CURSORTYPE_WESTPANNING: 28,
+  PP_CURSORTYPE_MOVE: 29,
+  PP_CURSORTYPE_VERTICALTEXT: 30,
+  PP_CURSORTYPE_CELL: 31,
+  PP_CURSORTYPE_CONTEXTMENU: 32,
+  PP_CURSORTYPE_ALIAS: 33,
+  PP_CURSORTYPE_PROGRESS: 34,
+  PP_CURSORTYPE_NODROP: 35,
+  PP_CURSORTYPE_COPY: 36,
+  PP_CURSORTYPE_NONE: 37,
+  PP_CURSORTYPE_NOTALLOWED: 38,
+  PP_CURSORTYPE_ZOOMIN: 39,
+  PP_CURSORTYPE_ZOOMOUT: 40,
+  PP_CURSORTYPE_GRAB: 41,
+  PP_CURSORTYPE_GRABBING: 42,
+};
+
+const PP_FileOpenFlags = {
+  PP_FILEOPENFLAG_READ: 1 << 0,
+  PP_FILEOPENFLAG_WRITE: 1 << 1,
+  PP_FILEOPENFLAG_CREATE: 1 << 2,
+  PP_FILEOPENFLAG_TRUNCATE: 1 << 3,
+  PP_FILEOPENFLAG_EXCLUSIVE: 1 << 4,
+  PP_FILEOPENFLAG_APPEND: 1 << 5
+};
+
+const PP_FileSystemType = {
+  PP_FILESYSTEMTYPE_INVALID: 0,
+  PP_FILESYSTEMTYPE_EXTERNAL: 1,
+  PP_FILESYSTEMTYPE_LOCALPERSISTENT: 2,
+  PP_FILESYSTEMTYPE_LOCALTEMPORARY: 3,
+  PP_FILESYSTEMTYPE_ISOLATED: 4
+};
+
+const PP_FileType = {
+  PP_FILETYPE_REGULAR: 0,
+  PP_FILETYPE_DIRECTORY: 1,
+  PP_FILETYPE_OTHER: 2
+};
+
+const PP_FlashLSORestrictions = {
+  PP_FLASHLSORESTRICTIONS_NONE: 1,
+  PP_FLASHLSORESTRICTIONS_BLOC: 2,
+  PP_FLASHLSORESTRICTIONS_IN_MEMORY: 3,
+};
+
+const PP_FlashSetting = {
+  PP_FLASHSETTING_3DENABLED: 1,
+  PP_FLASHSETTING_INCOGNITO: 2,
+  PP_FLASHSETTING_STAGE3DENABLED: 3,
+  PP_FLASHSETTING_LANGUAGE: 4,
+  PP_FLASHSETTING_NUMCORES: 5,
+  PP_FLASHSETTING_LSORESTRICTIONS: 6,
+  PP_FLASHSETTING_STAGE3DBASELINEENABLED: 7,
+};
+
+const PP_Graphics3DAttrib = {
+  PP_GRAPHICS3DATTRIB_ALPHA_SIZE: 0x3021,
+  PP_GRAPHICS3DATTRIB_BLUE_SIZE: 0x3022,
+  PP_GRAPHICS3DATTRIB_GREEN_SIZE: 0x3023,
+  PP_GRAPHICS3DATTRIB_RED_SIZE: 0x3024,
+  PP_GRAPHICS3DATTRIB_DEPTH_SIZE: 0x3025,
+  PP_GRAPHICS3DATTRIB_STENCIL_SIZE: 0x3026,
+  PP_GRAPHICS3DATTRIB_SAMPLES: 0x3031,
+  PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS: 0x3032,
+  PP_GRAPHICS3DATTRIB_NONE: 0x3038,
+  PP_GRAPHICS3DATTRIB_HEIGHT: 0x3056,
+  PP_GRAPHICS3DATTRIB_WIDTH: 0x3057,
+  PP_GRAPHICS3DATTRIB_SWAP_BEHAVIOR: 0x3093,
+  PP_GRAPHICS3DATTRIB_BUFFER_PRESERVED: 0x3094,
+  PP_GRAPHICS3DATTRIB_BUFFER_DESTROYED: 0x3095,
+  PP_GRAPHICS3DATTRIB_GPU_PREFERENCE: 0x11000,
+  PP_GRAPHICS3DATTRIB_GPU_PREFERENCE_LOW_POWER: 0x11001,
+  PP_GRAPHICS3DATTRIB_GPU_PREFERENCE_PERFORMANCE: 0x11002
+};
+
+const PP_ImageDataFormat = {
+  PP_IMAGEDATAFORMAT_BGRA_PREMUL: 0,
+  PP_IMAGEDATAFORMAT_RGBA_PREMUL: 1,
+};
+
+const PP_InputEvent_Class = {
+  PP_INPUTEVENT_CLASS_MOUSE: 1 << 0,
+  PP_INPUTEVENT_CLASS_KEYBOARD: 1 << 1,
+  PP_INPUTEVENT_CLASS_WHEEL: 1 << 2,
+  PP_INPUTEVENT_CLASS_TOUCH: 1 << 3,
+  PP_INPUTEVENT_CLASS_IME: 1 << 4
+};
+
+const PP_InputEvent_Modifier = {
+  PP_INPUTEVENT_MODIFIER_SHIFTKEY: 1 << 0,
+  PP_INPUTEVENT_MODIFIER_CONTROLKEY: 1 << 1,
+  PP_INPUTEVENT_MODIFIER_ALTKEY: 1 << 2,
+  PP_INPUTEVENT_MODIFIER_METAKEY: 1 << 3,
+  PP_INPUTEVENT_MODIFIER_ISKEYPAD: 1 << 4,
+  PP_INPUTEVENT_MODIFIER_ISAUTOREPEAT: 1 << 5,
+  PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN: 1 << 6,
+  PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN: 1 << 7,
+  PP_INPUTEVENT_MODIFIER_RIGHTBUTTONDOWN: 1 << 8,
+  PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY: 1 << 9,
+  PP_INPUTEVENT_MODIFIER_NUMLOCKKEY: 1 << 10,
+  PP_INPUTEVENT_MODIFIER_ISLEFT: 1 << 11,
+  PP_INPUTEVENT_MODIFIER_ISRIGHT: 1 << 12
+};
+
+const PP_InputEvent_MouseButton = {
+  PP_INPUTEVENT_MOUSEBUTTON_NONE: -1,
+  PP_INPUTEVENT_MOUSEBUTTON_LEFT: 0,
+  PP_INPUTEVENT_MOUSEBUTTON_MIDDLE: 1,
+  PP_INPUTEVENT_MOUSEBUTTON_RIGHT: 2,
+};
+
+const PP_InputEvent_Type = {
+  PP_INPUTEVENT_TYPE_UNDEFINED: -1,
+  PP_INPUTEVENT_TYPE_MOUSEDOWN: 0,
+  PP_INPUTEVENT_TYPE_MOUSEUP: 1,
+  PP_INPUTEVENT_TYPE_MOUSEMOVE: 2,
+  PP_INPUTEVENT_TYPE_MOUSEENTER: 3,
+  PP_INPUTEVENT_TYPE_MOUSELEAVE: 4,
+  PP_INPUTEVENT_TYPE_WHEEL: 5,
+  PP_INPUTEVENT_TYPE_RAWKEYDOWN: 6,
+  PP_INPUTEVENT_TYPE_KEYDOWN: 7,
+  PP_INPUTEVENT_TYPE_KEYUP: 8,
+  PP_INPUTEVENT_TYPE_CHAR: 9,
+  PP_INPUTEVENT_TYPE_CONTEXTMENU: 10,
+  PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: 11,
+  PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: 12,
+  PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: 13,
+  PP_INPUTEVENT_TYPE_IME_TEXT: 14,
+  PP_INPUTEVENT_TYPE_TOUCHSTART: 15,
+  PP_INPUTEVENT_TYPE_TOUCHMOVE: 16,
+  PP_INPUTEVENT_TYPE_TOUCHEND: 17,
+  PP_INPUTEVENT_TYPE_TOUCHCANCEL: 18
+};
+
+const PP_NetworkList_State = {
+  PP_NETWORKLIST_STATE_DOWN: 0,
+  PP_NETWORKLIST_STATE_UP: 1
+};
+
+const PP_NetworkList_Type = {
+  PP_NETWORKLIST_TYPE_UNKNOWN: 0,
+  PP_NETWORKLIST_TYPE_ETHERNET: 1,
+  PP_NETWORKLIST_TYPE_WIFI: 2,
+  PP_NETWORKLIST_TYPE_CELLULAR: 3
+};
+
+const PP_TextInput_Type_Dev = {
+  PP_TEXTINPUT_TYPE_DEV_NONE: 0,
+  PP_TEXTINPUT_TYPE_DEV_TEXT: 1,
+  PP_TEXTINPUT_TYPE_DEV_PASSWORD: 2,
+  PP_TEXTINPUT_TYPE_DEV_SEARCH: 3,
+  PP_TEXTINPUT_TYPE_DEV_EMAIL: 4,
+  PP_TEXTINPUT_TYPE_DEV_NUMBER: 5,
+  PP_TEXTINPUT_TYPE_DEV_TELEPHONE: 6,
+  PP_TEXTINPUT_TYPE_DEV_URL: 7
+};
+
+const PP_URLRequestProperty = {
+  PP_URLREQUESTPROPERTY_URL: 0,
+  PP_URLREQUESTPROPERTY_METHOD: 1,
+  PP_URLREQUESTPROPERTY_HEADERS: 2,
+  PP_URLREQUESTPROPERTY_STREAMTOFILE: 3,
+  PP_URLREQUESTPROPERTY_FOLLOWREDIRECTS: 4,
+  PP_URLREQUESTPROPERTY_RECORDDOWNLOADPROGRESS: 5,
+  PP_URLREQUESTPROPERTY_RECORDUPLOADPROGRESS: 6,
+  PP_URLREQUESTPROPERTY_CUSTOMREFERRERURL: 7,
+  PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS: 8,
+  PP_URLREQUESTPROPERTY_ALLOWCREDENTIALS: 9,
+  PP_URLREQUESTPROPERTY_CUSTOMCONTENTTRANSFERENCODING: 10,
+  PP_URLREQUESTPROPERTY_PREFETCHBUFFERUPPERTHRESHOLD: 11,
+  PP_URLREQUESTPROPERTY_PREFETCHBUFFERLOWERTHRESHOLD: 12,
+  PP_URLREQUESTPROPERTY_CUSTOMUSERAGENT: 13,
+};
+
+const PP_URLResponseProperty = {
+  PP_URLRESPONSEPROPERTY_URL: 0,
+  PP_URLRESPONSEPROPERTY_REDIRECTURL: 1,
+  PP_URLRESPONSEPROPERTY_REDIRECTMETHOD: 2,
+  PP_URLRESPONSEPROPERTY_STATUSCODE: 3,
+  PP_URLRESPONSEPROPERTY_STATUSLINE: 4,
+  PP_URLRESPONSEPROPERTY_HEADERS: 5
+};
+
+const PP_VarType = {
+  PP_VARTYPE_UNDEFINED: 0,
+  PP_VARTYPE_NULL: 1,
+  PP_VARTYPE_BOOL: 2,
+  PP_VARTYPE_INT32: 3,
+  PP_VARTYPE_DOUBLE: 4,
+  PP_VARTYPE_STRING: 5,
+  PP_VARTYPE_OBJECT: 6,
+  PP_VARTYPE_ARRAY: 7,
+  PP_VARTYPE_DICTIONARY: 8,
+  PP_VARTYPE_ARRAY_BUFFER: 9,
+  PP_VARTYPE_RESOURCE: 10,
+};
+
+const PP_Flash_Clipboard_Format = {
+  PP_FLASH_CLIPBOARD_FORMAT_INVALID: 0,
+  PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT: 1,
+  PP_FLASH_CLIPBOARD_FORMAT_HTML: 2,
+  PP_FLASH_CLIPBOARD_FORMAT_RTF: 3,
+};
+
+
+const PR_RDONLY = 0x01;
+const PR_WRONLY = 0x02;
+const PR_RDWR = 0x04;
+const PR_CREATE_FILE = 0x08;
+const PR_APPEND = 0x10;
+const PR_TRUNCATE = 0x20;
+const PR_SYNC = 0x40;
+const PR_EXCL = 0x80;
+
+
+class InterfaceMemberCall {
+  constructor(interfaceName, memberName, args) {
+    this.__interface = interfaceName;
+    this.__member = memberName;
+    Object.assign(this, args);
+  }
+}
+
+class InterfaceInstanceMemberCall {
+  constructor(interfaceName, instance, memberName, args) {
+    this.__interface = interfaceName;
+    this.__instance = instance;
+    this.__member = memberName;
+    Object.assign(this, args);
+  }
+}
+
+class CallbackCall {
+  constructor(callbackName, callback, args) {
+    this.__callback = callbackName;
+    this.__callbackStruct = callback;
+    Object.assign(this, args);
+  }
+}
+
+
+class OffscreenCanvas {
+  constructor(instance, width, height) {
+    this._canvas = instance.window.document.createElement("canvas");
+    this._canvas.width = width;
+    this._canvas.height = height;
+  }
+
+  set width(width) {
+    this._canvas.width = width;
+  }
+  set heigth(height) {
+    this._canvas.height = height;
+  }
+
+  getContext(contextId, contextOptions) {
+    return this._canvas.getContext(contextId, contextOptions);
+  }
+  transferToImageBitmap() {
+    let window = this._canvas.ownerDocument.defaultView;
+    return window.createImageBitmap(this._canvas);
+  }
+}
+
+
+class ObjectCache {
+  constructor(getkeyForLookup=(v) => v) {
+    this.objects = [];
+    this.getkeyForLookup = getkeyForLookup;
+  }
+
+  add(object) {
+    let i = 1;
+    while (i < this.objects.length && i in this.objects) {
+      ++i;
+    }
+    this.objects[i] = object;
+    return i;
+  }
+  lookup(i) {
+    return this.objects[this.getkeyForLookup(i)];
+  }
+  destroy(i) {
+    delete this.objects[this.getkeyForLookup(i)];
+  }
+}
+
+
+class PP_Var {
+  constructor(value, instance) {
+    if (typeof value == 'undefined' && this.constructor.field == "as_bool") {
+      value = PP_Bool.PP_FALSE;
+    }
+
+    this.type = this.constructor.type;
+    this.padding = 0;
+    this.value = {};
+    this.value[this.constructor.field] = this.constructor.convertValue(value, instance);
+  }
+
+  static get field() {
+    return this.fields[this.type];
+  }
+  static convertValue(value) {
+    return value;
+  }
+  static getAsJSValue(value) {
+    return value.value[this.field];
+  }
+  static normalize({ type, value }) {
+    let field = this.fields[type];
+    return {
+      type,
+      padding: 0,
+      value: {
+        [field]: value[field],
+      },
+    };
+  }
+}
+// Our JSON parser always needs a field for a union, so we use "as_bool" for
+// PP_VARTYPE_UNDEFINED/PP_VARTYPE_NULL.
+PP_Var.fields = [
+  "as_bool",   // PP_VARTYPE_UNDEFINED
+  "as_bool",   // PP_VARTYPE_NULL
+  "as_bool",   // PP_VARTYPE_BOOL
+  "as_int",    // PP_VARTYPE_INT32
+  "as_double", // PP_VARTYPE_DOUBLE
+  "as_id",     // PP_VARTYPE_STRING
+  "as_id",     // PP_VARTYPE_OBJECT
+  "as_id",     // PP_VARTYPE_ARRAY
+  "as_id",     // PP_VARTYPE_DICTIONARY
+  "as_id",     // PP_VARTYPE_ARRAY_BUFFER
+  "as_id",     // PP_VARTYPE_RESOURCE
+];
+PP_Var.type = PP_VarType.PP_VARTYPE_UNDEFINED;
+
+class PP_Var_Cached extends PP_Var {
+  static convertValue(value, instance) {
+    return this.cache.add({ value, instance, refcnt: 1 });
+  }
+  static getAsJSValue(value) {
+    return this.cache.lookup(value).value;
+  }
+  static getAsJSValueWithInstance(value) {
+    let r = this.cache.lookup(value);
+    return [r.value, r.instance];
+  }
+  static addRef(value) {
+    let cache = this.caches[value.type];
+    if (cache) {
+      ++cache.lookup(value).refcnt;
+    }
+  }
+  static release(value) {
+    let cache = this.caches[value.type];
+    if (cache && --cache.lookup(value).refcnt === 0) {
+      cache.destroy(value);
+    }
+  }
+  static get cache() {
+    return this.caches[this.type];
+  }
+  static isCached(type) {
+    return this.caches[type] !== undefined;
+  }
+}
+PP_Var_Cached.caches = PP_Var.fields.map((f) => f == "as_id" ? new ObjectCache((v) => v.value[f]) : undefined);
+
+class Undefined_PP_Var extends PP_Var {
+}
+Undefined_PP_Var.type = PP_VarType.PP_VARTYPE_UNDEFINED;
+
+class Null_PP_Var extends PP_Var {
+}
+Null_PP_Var.type = PP_VarType.PP_VARTYPE_NULL;
+
+class Bool_PP_Var extends PP_Var {
+  static convertValue(value) {
+    return (typeof value == 'boolean') ? (value ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE) : value;
+  }
+  static getAsJSValue(value) {
+    return PP_Bool[value.value[this.field]] == PP_Bool.PP_TRUE;
+  }
+}
+Bool_PP_Var.type = PP_VarType.PP_VARTYPE_BOOL;
+
+class Int32_PP_Var extends PP_Var {
+}
+Int32_PP_Var.type = PP_VarType.PP_VARTYPE_INT32;
+
+class Double_PP_Var extends PP_Var {
+}
+Double_PP_Var.type = PP_VarType.PP_VARTYPE_DOUBLE;
+
+class String_PP_Var extends PP_Var_Cached {
+}
+String_PP_Var.type = PP_VarType.PP_VARTYPE_STRING;
+
+class Object_PP_Var extends PP_Var_Cached {
+}
+Object_PP_Var.type = PP_VarType.PP_VARTYPE_OBJECT;
+
+class Array_PP_Var extends PP_Var_Cached {
+  constructor(array = [], instance) {
+    if (!Array.isArray(array)) {
+      throw new Error("Array_PP_Var is not constructed from an array.");
+    }
+    super(array, instance);
+  }
+}
+Array_PP_Var.type = PP_VarType.PP_VARTYPE_ARRAY;
+
+// Dictionary is a specialized class for the value of Dictionary_PP_Var only.
+class Dictionary {
+  constructor(object) {
+    Object.assign(this, object);
+  }
+}
+class Dictionary_PP_Var extends PP_Var_Cached {
+  constructor(dict = new Dictionary(), instance) {
+    if (!(dict instanceof Dictionary)) {
+      throw new Error("Dictionary_PP_Var is not constructed from a Dictionary.");
+    }
+    super(dict, instance);
+  }
+}
+Dictionary_PP_Var.type = PP_VarType.PP_VARTYPE_DICTIONARY;
+
+class ArrayBuffer_PP_Var extends PP_Var_Cached {
+  constructor(ab, instance) {
+    super((ab instanceof ArrayBuffer) ? ab : new ArrayBuffer(ab), instance);
+  }
+}
+ArrayBuffer_PP_Var.type = PP_VarType.PP_VARTYPE_ARRAYBUFFER
+
+class Resource_PP_Var extends PP_Var_Cached {
+}
+Resource_PP_Var.type = PP_VarType.PP_VARTYPE_RESOURCE;
+
+/* Convert a native JavaScript value to a object-based PP_Var */
+PP_Var.fromJSValue = function(v, instance) {
+  switch (typeof v) {
+    case "undefined":
+      return new PP_Var(undefined, instance);
+    case "boolean":
+      return new Bool_PP_Var(v, instance);
+    case "number":
+      if (Number.isInteger(v) && v >= (-(1 << 31)) && v <= ((1 << 31) - 1)) {
+        return new Int32_PP_Var(v, instance);
+      }
+      return new Double_PP_Var(v, instance);
+    case "string":
+      return new String_PP_Var(v, instance);
+    case "symbol":
+      throw new Error("Don't know how to convert Symbol to PP_Var.");
+    case "function":
+      /* falls through */
+    default:
+      if (v === null) {
+        return new Null_PP_Var(null, instance);
+      } else if (Array.isArray(v)) {
+        return v.map((value) => PP_Var.fromJSValue(value, instance));
+      } else if (v instanceof Dictionary) {
+        let dict = new Dictionary();
+        for (let [key, value] of Object.entries(v)) {
+          dict[key] = PP_Var.fromJSValue(value, instance);
+        }
+        return new Dictionary_PP_Var(dict, instance);
+      } else if (v instanceof ArrayBuffer) {
+        return new ArrayBuffer_PP_Var(v, instance);
+      }
+      return new Object_PP_Var(v, instance);
+  }
+};
+
+/* Convert a json-based PP_Var to a native JavaScript value */
+PP_Var.toJSValue = function(json, instance) {
+  if (!("type" in json) || !("padding" in json) || !("value" in json)) {
+    return undefined;
+  }
+  let type = json.type;
+  // Sometimes json.type is a String instead of a Number
+  if (typeof type === "string") {
+    type = PP_VarType[json.type];
+  }
+  switch (type) {
+    case PP_VarType.PP_VARTYPE_UNDEFINED:
+      return undefined;
+    case PP_VarType.PP_VARTYPE_NULL:
+      return null;
+    case PP_VarType.PP_VARTYPE_BOOL:
+      return Boolean(Bool_PP_Var.getAsJSValue(json, instance));
+    case PP_VarType.PP_VARTYPE_INT32:
+      return Number.parseInt(String(Int32_PP_Var.getAsJSValue(json, instance)), 10);
+    case PP_VarType.PP_VARTYPE_DOUBLE:
+      return Number.parseFloat(String(Double_PP_Var.getAsJSValue(json, instance)));
+    case PP_VarType.PP_VARTYPE_STRING:
+      return String(String_PP_Var.getAsJSValue(json, instance));
+    case PP_VarType.PP_VARTYPE_OBJECT:
+      return Object(Object_PP_Var.getAsJSValue(json, instance));
+    case PP_VarType.PP_VARTYPE_ARRAY:
+      return Array_PP_Var.getAsJSValue(json, instance).map((v) => PP_Var.toJSValue(v, instance));
+    case PP_VarType.PP_VARTYPE_DICTIONARY:
+      let dict = new Dictionary();
+      for (let [key, value] of Object.entries(Dictionary_PP_Var.getAsJSValue(json, instance))) {
+        dict[key] = PP_Var.toJSValue(value, instance);
+      }
+      return dict;
+    case PP_VarType.PP_VARTYPE_ARRAY_BUFFER:
+      return ArrayBuffer_PP_Var.getAsJSValue(json.value, instance);
+    case PP_VarType.PP_VARTYPE_RESOURCE:
+      return Resource_PP_Var.getAsJSValue(json.value, instance);
+    default:
+      throw new Error("Don't know how to convert PP_Var with type(" + type + ") to a proper JavaScript object.");
+  }
+}
+
+class PP_Resource {
+  constructor(instance) {
+    // XXX Need to check that this is correct!
+    this.refcnt = 1;
+    this.id = PP_Resource.cache.add(this);
+    this.instance = instance;
+  }
+
+  destroy() {
+    PP_Resource.cache.destroy(this.id);
+  }
+
+  addRef() {
+    ++this.refcnt;
+  }
+  release() {
+    if (--this.refcnt === 0) {
+      this.destroy();
+    }
+  }
+
+  toJSON() {
+    return this.id;
+  }
+
+  static lookup(id) {
+    return this.cache.lookup(id);
+  }
+}
+PP_Resource.cache = new ObjectCache();
+
+const INT16_MIN = -Math.pow(2, 15);
+const INT16_MAX = Math.pow(2, 15) - 1;
+const negDiv = SIMD.Float32x4.splat(-INT16_MIN);
+const posDiv = SIMD.Float32x4.splat(INT16_MAX);
+const zeroFloat32 = SIMD.Float32x4.splat(0);
+const littleEndian = (new Uint8Array(Uint32Array.of(0xdeadbeef).buffer))[0] == 0xef;
+
+class Audio extends PP_Resource {
+  constructor(instance, bufferSize, frameCount, callback, data) {
+    super(instance);
+    let rt = instance.rt;
+    let mem = rt.allocateCachedBuffer(bufferSize);
+    let samples = new Float32Array(frameCount * 2);
+    let left = new Float32Array(samples.buffer, 0, frameCount);
+    let right = new Float32Array(samples.buffer, frameCount * 4, frameCount);
+
+    // FIXME Wish we could use MSE, but it doesn't have what we need yet.
+    this.context = new instance.window.AudioContext();
+    this.callbackNode = this.context.createScriptProcessor(frameCount, 0, 2);
+    this.callbackNode.addEventListener("audioprocess", (e) => {
+      rt.call(new CallbackCall("PPB_Audio_Callback_1_0", callback, { sample_buffer: mem, buffer_size_in_bytes: bufferSize, user_data: data }),
+              true);
+      let buffer = new Int16Array(rt.getCachedBuffer(mem));
+
+      // FIXME Ideally we'd convert straight into the outputBuffer's channel
+      //       data, but because our AudioContext is from the window we get
+      //       CCWs here, and the SIMD code we use can't deal with those.
+      //let left = e.outputBuffer.getChannelData(0);
+      //let right = e.outputBuffer.getChannelData(1);
+      // Sigh, have to convert between Int16 and Float32 (in a range between
+      // -1 and 1).
+      this.constructor.splitAndConvertInt16toFloat32(buffer, left, right);
+      e.outputBuffer.copyToChannel(left, 0, 0);
+      e.outputBuffer.copyToChannel(right, 1, 0);
+    });
+  }
+
+  static splitAndConvertInt16toFloat32(buffer, leftResult, rightResult) {
+    // We'll process groups of 4 frames (8 samples, 4 left and 4 right,
+    // interleaved as [l1, r1, l2, r2, l3, r3, l4, r4]). We load as Int32x4, to
+    // make sure the JIT inlines, so our data really looks like
+    // [r1l1, r2l2, r3l3, r4l4] or [l1r1, l2r2, l3r3, l4r4], depending on
+    // endianness.
+    let firstResult, secondResult;
+    if (littleEndian) {
+      firstResult = rightResult;
+      secondResult = leftResult;
+    } else {
+      firstResult = leftResult;
+      secondResult = rightResult;
+    }
+    let frameCount = leftResult.length;
+    for (let i = 0; i < frameCount; i += 4) {
+      let interleaved = SIMD.Int32x4.load(buffer, i * 2);
+
+      let first = SIMD.Int32x4.shiftRightByScalar(interleaved, 16);
+      // Convert to float.
+      first = SIMD.Float32x4.fromInt32x4(first);
+      // Divide positive numbers by INT16_MAX and negative numbers by -INT16_MIN,
+      // to have a Float32 in a range between -1 and 1.
+      let pos = SIMD.Float32x4.greaterThan(first, zeroFloat32);
+      first = SIMD.Float32x4.div(first,
+                                 SIMD.Float32x4.select(pos, posDiv, negDiv));
+
+      let second = SIMD.Int32x4.shiftLeftByScalar(interleaved, 16);
+      second = SIMD.Int32x4.shiftRightByScalar(second, 16);
+      // Convert to float.
+      second = SIMD.Float32x4.fromInt32x4(second);
+      // Divide positive numbers by INT16_MAX and negative numbers by -INT16_MIN,
+      // to have a Float32 in a range between -1 and 1.
+      pos = SIMD.Float32x4.greaterThan(second, zeroFloat32);
+      second = SIMD.Float32x4.div(second,
+                                  SIMD.Float32x4.select(pos, posDiv, negDiv));
+
+      SIMD.Float32x4.store(firstResult, i, first);
+      SIMD.Float32x4.store(secondResult, i, second);
+    }
+  }
+
+  start() {
+    this.callbackNode.connect(this.context.destination);
+  }
+  stop() {
+    this.callbackNode.disconnect(this.context.destination);
+  }
+}
+class AudioConfig extends PP_Resource {
+  constructor(instance, frameCount) {
+    super(instance);
+    this.frameCount = frameCount;
+    this.bufferSize = frameCount * 2 /* channels */ * 2 /* bytes per frame */;
+  }
+}
+class BrowserFont_Trusted extends PP_Resource {
+  constructor(instance, description) {
+    super(instance);
+    this.description = description;
+    this.customFamily = undefined;
+    if (PP_VarType[this.description.face.type] == PP_VarType.PP_VARTYPE_STRING) {
+        this.customFamily = String_PP_Var.getAsJSValue(description.face);
+    }
+  }
+
+  get fontRule() {
+    if (!("_fontRule" in this)) {
+      let fontRule = [];
+      if (PP_Bool[this.description.italic] == PP_Bool.PP_TRUE) {
+        fontRule.push("italic");
+      }
+      if (PP_Bool[this.description.small_caps] == PP_Bool.PP_TRUE) {
+        fontRule.push("small-caps");
+      }
+      let weight;
+      switch (PP_BrowserFont_Trusted_Weight[this.description.weight]) {
+        case PP_BrowserFont_Trusted_Weight.PP_BROWSERFONT_TRUSTED_WEIGHT_100:
+          weight = 100;
+          break;
+        case PP_BrowserFont_Trusted_Weight.PP_BROWSERFONT_TRUSTED_WEIGHT_200:
+          weight = 200;
+          break;
+        case PP_BrowserFont_Trusted_Weight.PP_BROWSERFONT_TRUSTED_WEIGHT_300:
+          weight = 300;
+          break;
+        case PP_BrowserFont_Trusted_Weight.PP_BROWSERFONT_TRUSTED_WEIGHT_400:
+          weight = 400;
+          break;
+        case PP_BrowserFont_Trusted_Weight.PP_BROWSERFONT_TRUSTED_WEIGHT_500:
+          weight = 500;
+          break;
+        case PP_BrowserFont_Trusted_Weight.PP_BROWSERFONT_TRUSTED_WEIGHT_600:
+          weight = 600;
+          break;
+        case PP_BrowserFont_Trusted_Weight.PP_BROWSERFONT_TRUSTED_WEIGHT_700:
+          weight = 700;
+          break;
+        case PP_BrowserFont_Trusted_Weight.PP_BROWSERFONT_TRUSTED_WEIGHT_800:
+          weight = 800;
+          break;
+        case PP_BrowserFont_Trusted_Weight.PP_BROWSERFONT_TRUSTED_WEIGHT_900:
+          weight = 900;
+          break;
+      }
+      fontRule.push(weight);
+      fontRule.push(this.description.size + "px");
+      let family;
+      if (PP_VarType[this.description.face.type] == PP_VarType.PP_VARTYPE_UNDEFINED) {
+        switch (PP_BrowserFont_Trusted_Family[this.description.family]) {
+          case PP_BrowserFont_Trusted_Family.PP_BROWSERFONT_TRUSTED_FAMILY_DEFAULT:
+            throw new Error("Don't know default font.");
+          case PP_BrowserFont_Trusted_Family.PP_BROWSERFONT_TRUSTED_FAMILY_SERIF:
+            family = "serif";
+            break;
+          case PP_BrowserFont_Trusted_Family.PP_BROWSERFONT_TRUSTED_FAMILY_SANSSERIF:
+            family = "sans-serif";
+            break;
+          case PP_BrowserFont_Trusted_Family.PP_BROWSERFONT_TRUSTED_FAMILY_MONOSPACE:
+            family = "monospace";
+            break;
+        }
+      } else {
+        family = this.customFamily;
+      }
+
+      // This shouldn't happen, but make sure there is a font family assigned.
+      family = (family == "") ? "serif" : family;
+
+      fontRule.push(family);
+      if (this.description.letter_spacing > 0) {
+        //throw new Error("Need to implement support for letter_spacing.");
+      }
+      if (this.description.word_spacing > 0) {
+        //throw new Error("Need to implement support for word_spacing.");
+      }
+      this._fontRule = fontRule.join(" ");
+    }
+    return this._fontRule;
+  }
+  measureText(text) {
+    // FIXME Can we avoid creating a context?
+    if (!("_context" in this)) {
+      let canvas = this.instance.window.document.createElement("canvas");
+      this._context = canvas.getContext("2d");
+      this._context.font = this.fontRule;
+    }
+    let metrics = this._context.measureText(text);
+    return Math.round(metrics.width);
+  }
+}
+class Buffer extends PP_Resource {
+  constructor(instance, size) {
+    super(instance);
+    this.size = size;
+    this.mappedCount = 0;
+  }
+
+  map() {
+    if (++this.mappedCount == 1) {
+      this.mem = this.instance.rt.allocateCachedBuffer(this.size);
+    }
+    return this.mem;
+  }
+  unmap() {
+    if (--this.mappedCount == 0) {
+      this.instance.rt.freeCachedBuffer(this.mem);
+      delete this.mem;
+    }
+  }
+}
+class Flash_MessageLoop extends PP_Resource {
+  run() {
+    this._running = true;
+    let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
+    while (this._running) {
+      thread.processNextEvent(true);
+    }
+  }
+  quit() {
+    this._running = false;
+  }
+}
+class Graphics extends PP_Resource {
+  constructor(instance) {
+    super(instance);
+    this.canvas = instance.window.document.createElement("canvas");
+  }
+  destroy() {
+    this.canvas.remove();
+    super.destroy();
+  }
+  changeSize(width, height) {
+    this.canvas.width = width;
+    this.canvas.height = height;
+  }
+}
+class Graphics2DPaintOperation {
+  constructor(imageData, x, y, dirtyRect=[]) {
+    this.imageData = imageData;
+    this.imageData.addRef();
+    this.x = x;
+    this.y = y;
+    this.dirtyRect = dirtyRect;
+  }
+
+  destroy() {
+    this.imageData.release();
+  }
+
+  execute(context) {
+    context.putImageData(this.imageData.getDOMImageData(), this.x, this.y, ...this.dirtyRect);
+  }
+}
+class Graphics2DScrollOperation {
+  constructor(clipRect, amountX, amountY) {
+    this.clipRect = clipRect;
+    this.amountX = amountX;
+    this.amountY = amountY;
+  }
+
+  destroy() {
+    // Nothing to do here.
+  }
+
+  execute(context) {
+    let clip = context.getImageData(...this.clipRect);
+    context.putImageData(clip, this.clipRect[0] + this.amountX, this.clipRect[1] + this.amountY);
+  }
+}
+class Graphics2D extends Graphics {
+  constructor(instance, width, height) {
+    super(instance);
+
+    // FIXME We should probably do transferControlToOffscreen instead, once
+    //       that's available.
+    this.bitmapContext = this.canvas.getContext("bitmaprenderer");
+    this.offscreen = new OffscreenCanvas(instance, width, height);
+    this.context = this.offscreen.getContext("2d");
+    this.changeSize(width, height);
+    this.operations = [];
+  }
+
+  destroy() {
+    this.clearOperations();
+    super.destroy();
+  }
+
+  changeSize(width, height) {
+    this.offscreen.width = width;
+    this.offscreen.height = height;
+    super.changeSize(width, height);
+    this.bitmapContext.width = width;
+    this.bitmapContext.height = height;
+  }
+
+  addOperation(operation) {
+    this.operations.push(operation);
+  }
+
+  clearOperations() {
+    for (let operation of this.operations) {
+      operation.destroy();
+    }
+    this.operations = [];
+  }
+
+  flush(callback) {
+    for (let operation of this.operations) {
+      operation.execute(this.context);
+      operation.destroy();
+    }
+    this.operations = [];
+    //dump(this.canvas.toDataURL());
+
+    this.offscreen.transferToImageBitmap().then((bitmap) => {
+      //dump(this.offscreen._canvas.toDataURL());
+      this.bitmapContext.transferImageBitmap(bitmap);
+      this.instance.rt.call(new CallbackCall("PP_CompletionCallback", callback, { result: PP_OK }));
+    }, () => {
+      this.instance.rt.call(new CallbackCall("PP_CompletionCallback", callback, { result: PP_ERROR_FAILED }));
+    });
+    return PP_OK_COMPLETIONPENDING;
+  }
+}
+class Graphics3D extends Graphics {
+  constructor(instance, attributes) {
+    let width = -1, height = -1;
+    let contextAttributes = {};
+    for (let [k, v] of attributes.entries()) {
+      switch (k) {
+        case PP_Graphics3DAttrib.PP_GRAPHICS3DATTRIB_DEPTH_SIZE:
+          contextAttributes.depth = v > 0;
+          break;
+        case PP_Graphics3DAttrib.PP_GRAPHICS3DATTRIB_STENCIL_SIZE:
+          contextAttributes.stencil = v > 0;
+          break;
+        case PP_Graphics3DAttrib.PP_GRAPHICS3DATTRIB_HEIGHT:
+          height = v;
+          break;
+        case PP_Graphics3DAttrib.PP_GRAPHICS3DATTRIB_WIDTH:
+          width = v;
+          break;
+        case PP_Graphics3DAttrib.PP_GRAPHICS3DATTRIB_SWAP_BEHAVIOR:
+          contextAttributes.preserveDrawingBuffer = v == PP_Graphics3DAttrib.PP_GRAPHICS3DATTRIB_BUFFER_PRESERVED;
+          break;
+        case PP_Graphics3DAttrib.PP_GRAPHICS3DATTRIB_GPU_PREFERENCE:
+          contextAttributes.preferLowPowerToHighPerformance = v == PP_Graphics3DAttrib.PP_GRAPHICS3DATTRIB_GPU_PREFERENCE_LOW_POWER;
+          break;
+        // FIXME Deal with other attributes!
+      }
+    }
+    // FIXME WORKAROUND LINUX WebGL BUG.
+    //contextAttributes.antialias = false;
+    let window = instance.window;
+    if (width < 0) {
+      width = window.innerWidth * window.devicePixelRatio;
+    }
+    if (height < 0) {
+      height = window.innerHeight * window.devicePixelRatio;
+    }
+
+    super(instance);
+    this.context = this.canvas.getContext("webgl", contextAttributes);
+    this.changeSize(width, height);
+  }
+
+  changeSize(width, height) {
+    super.changeSize(width, height);
+    this.context.viewport(0, 0, this.context.drawingBufferWidth, this.context.drawingBufferHeight);
+  }
+  flush(callback) {
+    this.context.flush();
+    this.instance.window.requestAnimationFrame(() => {
+      this.instance.rt.call(new CallbackCall("PP_CompletionCallback", callback, { result: PP_OK }));
+    });
+    return PP_OK_COMPLETIONPENDING;
+  }
+  get objects() {
+    if (!("_objects" in this)) {
+      this._objects = new ObjectCache();
+    }
+    return this._objects;
+  }
+  get mappedTextures() {
+    if (!("_mappedTextures" in this)) {
+      this._mappedTextures = new Map();
+    }
+    return this._mappedTextures;
+  }
+}
+class ImageData extends PP_Resource {
+  constructor(instance, format, size) {
+    let cached = instance.cachedImageData;
+    if (cached &&
+        cached.format == format &&
+        cached.size.width == size.width &&
+        cached.size.height == size.height) {
+      return cached;
+    }
+
+    super(instance);
+    this.format = format;
+    this.size = size;
+    this.mapped = null;
+  }
+
+  destroy() {
+    if (this.mapped) {
+      this.instance.rt.freeCachedBuffer(this.mapped);
+      this.mappedSize = 0;
+      this.mapped = null;
+    }
+    super.destroy();
+  }
+
+  map() {
+    if (!this.mapped) {
+      this.mappedSize = this.size.height * this.stride;
+      this.mapped = this.instance.rt.allocateCachedBuffer(this.mappedSize);
+    }
+    return this.mapped;
+  }
+
+  // You should surround any code drawing to the context with
+  // beginDrawing/endDrawing calls.
+  get context() {
+    if (!("_context" in this)) {
+      let canvas = this.instance.window.document.createElement("canvas");
+      canvas.width = this.size.width;
+      canvas.height = this.size.height;
+      this._context = canvas.getContext("2d");
+    }
+    return this._context;
+  }
+  beginDrawing() {
+    this.map();
+    this.context.save();
+    let currentData = new Uint8ClampedArray(this.instance.rt.getCachedBuffer(this.mapped));
+    this.context.putImageData(new this.instance.window.ImageData(currentData, this.size.width, this.size.height), 0, 0);
+    return this.context;
+  }
+  endDrawing() {
+    let data = this.context.getImageData(0, 0, this.size.width, this.size.height).data;
+    if (this.mappedSize != data.byteLength) {
+      debugger;
+      throw new Error("Trying to set too " + (this.mappedSize > data.byteLength ? "little" : "much") + " data, src: " + data.byteLength + " dest: " + this.mappedSize + ".\n");
+    }
+    this.instance.rt.setBuffer(this.mapped, data);
+    this.context.restore();
+  }
+  get stride() {
+    return this.size.width * 4;
+  }
+  getDOMImageData() {
+    let dataArray = new Uint8ClampedArray(this.instance.rt.getCachedBuffer(this.mapped));
+    this._colorConvert(dataArray);
+    let imagedata = new this.instance.window.ImageData(dataArray, this.size.width, this.size.height);
+    return imagedata;
+  }
+  _colorConvert(dataArray) {
+    if (this.format == PP_ImageDataFormat.PP_IMAGEDATAFORMAT_BGRA_PREMUL) {
+      let tmp;
+      for (let i = 0; i < dataArray.length; i += 4) {
+        tmp = dataArray[i];
+        dataArray[i] = dataArray[i + 2];
+        dataArray[i + 2] = tmp;
+      }
+    }
+  }
+}
+class InputEvent extends PP_Resource {
+  constructor(instance, event) {
+    super(instance);
+    this.eventType = EventTypes.get(event.type).eventType;
+    this.domEvent = event;
+    this.timeStamp = event.timeStamp / 1000;
+  }
+}
+class NetworkMonitor extends PP_Resource {
+}
+class PrintingDev extends PP_Resource {
+}
+class TCPSocketPrivate extends PP_Resource {
+  constructor(instance) {
+    super(instance);
+  }
+
+  callCallback(callback, result) {
+    this.instance.rt.call(new CallbackCall("PP_CompletionCallback", callback, { result }));
+    this.impl.onerror = null;
+  }
+  connect(host, port, callback) {
+    this.impl = new TCPSocket(host, port, { binaryType: "arraybuffer" });
+    this.impl.onopen = (e) => {
+      this.impl.suspend();
+
+      this.callCallback(callback, PP_OK);
+      this.impl.onopen = null;
+    };
+    this.impl.onerror = (e) => {
+      this.callCallback(callback, PP_ERROR_FAILED);
+      this.impl.onopen = null;
+    };
+  }
+  read(buffer, bytesToRead, callback) {
+    this.impl.ondata = (e) => {
+      e.target.suspend();
+      if (e.data) {
+        if (e.data.byteLength > bytesToRead) {
+          throw new Error("We need to cache the data.");
+        }
+        this.instance.rt.setBuffer(buffer, e.data);
+        this.callCallback(callback, e.data.byteLength);
+      } else {
+        this.callCallback(callback, PP_ERROR_FAILED);
+      }
+      this.impl.ondata = null;
+    };
+    this.impl.onerror = (e) => {
+      this.callCallback(callback, PP_ERROR_FAILED);
+      this.impl.ondata = null;
+    };
+    this.impl.resume();
+  }
+  write(data) {
+    return this.impl.send(Uint8ClampedArray.from(data).buffer);
+  }
+  close() {
+    this.impl.close();
+    this.impl = null;
+  }
+  get localAddress() {
+    if (this.impl.readyState != "open") {
+      return null;
+    }
+    //let address = socket.wrappedJSObject._transport.getScriptableSelfAddr();
+    return "127.0.0.0";
+  }
+  get remoteAddress() {
+    if (this.impl.readyState != "open") {
+      return null;
+    }
+    //let address = socket.wrappedJSObject._transport.getScriptablePeerAddr();
+    return "127.0.0.0";
+  }
+}
+class URLLoader extends PP_Resource {
+  constructor(instance) {
+    super(instance);
+    this.responseReadCallback = null;
+    this.responseUnreadChunks = [];
+  }
+
+  openURL(method, url, callback) {
+    // FIXME Should we use this.instance.info.embed.window if the plugin
+    //       doesn't call PPB_URLLoaderTrusted::GrantUniversalAccess?
+    this.req = new this.instance.window.XMLHttpRequest({ mozSystem: true });
+
+    this.req.responseType = "moz-chunked-arraybuffer";
+    this.headersReceivedCallback = callback;
+    this.req.onreadystatechange = this.onreadystatechange.bind(this);
+    this.req.onprogress = this.onprogress.bind(this);
+
+    url = new this.instance.window.URL(url, this.instance.info.url);
+    this.req.open(method, url, true);
+  }
+  onreadystatechange() {
+    switch (this.req.readyState) {
+      case this.req.OPENED:
+        this.req.send();
+        let channel = this.req.channel;
+        try {
+          if (channel.QueryInterface(Ci.nsIFileChannel)) {
+            channel.contentType = "application/x-unknown-content-type";
+            channel.loadFlags |= Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
+          }
+        } catch (e) {
+        }
+        //this.req.overrideMimeType("application/x-unknown-content-type");
+        break;
+      case this.req.HEADERS_RECEIVED:
+        // Send callback.
+        this.headersReceivedCallback(0);
+        break;
+      case this.req.DONE:
+        if (this.responseReadCallback) {
+          let callback = this.responseReadCallback;
+          this.responseReadCallback = null;
+          callback();
+        }
+        break;
+    }
+  }
+  onprogress(e) {
+    this.bytes_received = e.loaded;
+    this.total_bytes_to_be_received = e.lengthComputable ? e.total : -1;
+    let chunk = this.req.response;
+    if (chunk.byteLength > 0) {
+      this.responseUnreadChunks.push(new Uint8Array(chunk));
+      if (this.responseReadCallback) {
+        let callback = this.responseReadCallback;
+        this.responseReadCallback = null;
+        callback();
+      }
+    }
+  }
+  get responseInfo() {
+    return new URLResponseInfo(this);
+  }
+  readResponseIntoBuffer(buffer, bytesToRead) {
+    let chunks = this.responseUnreadChunks;
+    let bytesRead = 0;
+    let i;
+    for (i = 0; i < chunks.length; ++i) {
+      let chunk = chunks[i];
+      let readFromChunk = Math.min(chunk.length, bytesToRead - bytesRead);
+      this.instance.rt.setBuffer(buffer + bytesRead, chunk, readFromChunk);
+      bytesRead += readFromChunk;
+      if (bytesRead == bytesToRead) {
+        // We've read all we need to read.
+        if (readFromChunk < chunk.length) {
+          // But we haven't consumed all the data of the last chunk we read
+          // from, store a new view into the buffer that only contains what we
+          // haven't read.
+          chunks[i] = new Uint8Array(chunk.buffer, chunk.byteOffset + readFromChunk);
+        } else {
+          ++i;
+        }
+        break;
+      }
+    }
+    if (i > 0) {
+      this.responseUnreadChunks = chunks.slice(i);
+    }
+    return bytesRead;
+  }
+  readResponse(buffer, bytesToRead, callback) {
+    if (this.responseUnreadChunks.length > 0 ||
+        this.req.readyState == this.req.DONE) {
+      callback(this.readResponseIntoBuffer(buffer, bytesToRead));
+      return -1;
+    }
+
+    this.responseReadCallback = () => {
+      callback(this.readResponseIntoBuffer(buffer, bytesToRead));
+    };
+    return -1;
+  }
+}
+class URLRequestInfo extends PP_Resource {
+  constructor(instance) {
+    super(instance);
+    this.propertyMap = new Map();
+  }
+
+  getProperty(property) {
+    return this.propertyMap.get(property);
+  }
+  setProperty(property, value) {
+    this.propertyMap.set(property, value);
+  }
+}
+class URLResponseInfo extends PP_Resource {
+  constructor(loader) {
+    super(loader.instance);
+    this.req = loader.req;
+  }
+
+  getProperty(property) {
+    switch (property) {
+      case PP_URLResponseProperty.PP_URLRESPONSEPROPERTY_URL:
+        return new String_PP_Var(this.req.responseURL);
+      case PP_URLResponseProperty.PP_URLRESPONSEPROPERTY_STATUSCODE:
+        return new Int32_PP_Var(this.req.status);
+      case PP_URLResponseProperty.PP_URLRESPONSEPROPERTY_STATUSLINE:
+        return new String_PP_Var(this.req.statusText);
+      case PP_URLResponseProperty.PP_URLRESPONSEPROPERTY_HEADERS:
+        return new String_PP_Var(this.req.getAllResponseHeaders().split("\r\n").join("\n"));
+    }
+  }
+}
+class View extends PP_Resource {
+}
+
+class IMEInputEvent extends InputEvent {
+  constructor (instance, event) {
+    super(instance, event);
+    let clauseArray = event.ranges;
+    if (event.type == "text" && clauseArray) {
+      for (let i = 0; i < clauseArray.length - 1; ++i) {
+        if (clauseArray[i].isTargetClause) {
+          this.targetSegment = i;
+          break;
+        }
+      }
+      this.segmentOffset = [];
+      this.segmentOffset[0] = 0;
+      let data = event.data;
+      let encoder = new TextEncoder("utf-8");
+      for (let i = 0, len = 0; i < clauseArray.length - 1; ++i) {
+        for (let j = clauseArray[i].startOffset; j < clauseArray[i].endOffset; ++j) {
+          len += encoder.encode(data[j]).length;
+        }
+        this.segmentOffset[i + 1] = len;
+      }
+    }
+  }
+}
+class KeyboardInputEvent extends InputEvent {
+}
+class MouseInputEvent extends InputEvent {
+}
+class TouchInputEvent extends InputEvent {
+}
+class WheelInputEvent extends InputEvent {
+}
+
+const EventTypeArray = [
+  [PP_InputEvent_Class.PP_INPUTEVENT_CLASS_KEYBOARD,
+   KeyboardInputEvent,
+   [["keydown", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_KEYDOWN],
+    ["keyup", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_KEYUP],
+    ["keypress", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_CHAR]]],
+  [PP_InputEvent_Class.PP_INPUTEVENT_CLASS_WHEEL,
+   WheelInputEvent,
+   [["wheel", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_WHEEL]]],
+  [PP_InputEvent_Class.PP_INPUTEVENT_CLASS_MOUSE,
+   MouseInputEvent,
+   [["mousedown", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_MOUSEDOWN],
+    ["mouseup", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_MOUSEUP],
+    ["mousemove", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_MOUSEMOVE],
+    ["mouseenter", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_MOUSEENTER],
+    ["mouseleave", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_MOUSELEAVE],
+    ["contextmenu", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_CONTEXTMENU]]],
+  [PP_InputEvent_Class.PP_INPUTEVENT_CLASS_IME,
+   IMEInputEvent,
+   [["compositionstart", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_IME_COMPOSITION_START],
+    ["text", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE],
+    ["compositionend", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_IME_COMPOSITION_END],
+    //There is no strict equivalent in Gecko for PP_INPUTEVENT_TYPE_IME_TEXT.
+    //["text", PP_InputEvent_Type.PP_INPUTEVENT_TYPE_IME_TEXT]
+    ]],
+];
+
+// Map from PP_InputEvent_Class to an array of DOM event name strings.
+const EventTypesByClass = EventTypeArray.reduce((map, [eventClass, resourceCtor, events]) => {
+    map.set(eventClass, events.map(([domEvent, ppapiEvent]) => domEvent));
+    return map;
+  }, new Map());
+
+// Map from DOM event name to an object with properties resourceCtor (PP_Resource constructor) and eventType (PP_InputEvent_Type value).
+const EventTypes = EventTypeArray.reduce((map, [eventClass, resourceCtor, events]) => {
+    events.forEach(([domEvent, ppapiEvent]) => {
+      map.set(domEvent, { resourceCtor: resourceCtor, eventType: ppapiEvent, eventClass: eventClass });
+    });
+    return map;
+  }, new Map());
+
+// Map from PP_InputEvent_Type to DOM event name.
+const EventByTypes = EventTypeArray.reduce((map, [eventClass, resourceCtor, events]) => {
+    events.forEach(([domEvent, ppapiEvent]) => {
+      map.set(ppapiEvent, domEvent);
+    });
+    return map;
+  }, new Map());
+
+// Special case for PP_INPUTEVENT_TYPE_IME_TEXT, there is no strict equivalent
+// in Gecko, use compositionend.
+EventByTypes.set(PP_InputEvent_Type.PP_INPUTEVENT_TYPE_IME_TEXT, "compositionend");
+
+const ModifierMap = [
+  [ "Shift", PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_SHIFTKEY ],
+  [ "Control", PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_CONTROLKEY ],
+  [ "Alt", PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ALTKEY ],
+  [ "Meta", PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_METAKEY ],
+  [ "CapsLock", PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY ],
+  [ "NumLock", PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_NUMLOCKKEY ],
+];
+
+// An abstract object to expose and handle all viewport-related properties and
+// operations, so we can seperate the UI logic from other parts in runtime.
+// Methods below can be overridden separately in UI layer by creating a
+// "createCustomViewport" function in "viewer.html" returning an object with the
+// same methods. It is useful if you want to customize your layout in UI layer.
+class PPAPIViewport {
+  constructor(instance) {
+    this._window = instance.window;
+    this._defaultEventTarget = instance.eventHandler;
+
+    if (typeof this._window.createCustomViewport === "function") {
+      let customViewport = this._window.createCustomViewport(
+          instance.viewportActionHandler.bind(instance));
+      Object.setPrototypeOf(customViewport, this);
+      return customViewport;
+    }
+  }
+
+  // Appends a canvas to the viewport
+  addView(canvas) {
+    this._window.document.body.appendChild(canvas);
+  }
+
+  // Removes all canvases from the viewport
+  clearView() {
+    Array.from(this._window.document.body.getElementsByTagName("canvas"))
+      .forEach(canvas => canvas.remove());
+  }
+
+  // Returns the size of the viewport and its position relative to the window.
+  getBoundingClientRect() {
+    return this._window.document.body.getBoundingClientRect();
+  }
+
+  // Returns a Boolean indicating whether an element is considered the viewport.
+  is(element) {
+    return element == this._window.document.body;
+  }
+
+  // Binds the specified listener on the viewport. Note that it will find the
+  // right DOM element to bind according to the event type.
+  bindUIEvent(type, listener) {
+    this._getEventTarget(type).addEventListener(type, listener);
+  }
+
+  // Removes the specified listener from the viewport.
+  unbindUIEvent(type, listener) {
+    this._getEventTarget(type).removeEventListener(type, listener);
+  }
+
+  // Sets the mouse cursor style of the viewport
+  setCursor(cursor) {
+    this._window.document.body.style.cursor = cursor;
+  }
+
+  // Returns the position that the viewport has already been scrolled.
+  getScrollOffset() {
+    return {
+      x: this._window.scrollX,
+      y: this._window.scrollY
+    };
+  }
+
+  // PRIVATE: Returns the right DOM element for event binding.
+  _getEventTarget(type) {
+    switch(type) {
+      case 'fullscreenchange':
+      case 'MozScrolledAreaChanged':
+        return this._window.document;
+      case 'resize':
+      case 'focus':
+      case 'blur':
+        return this._window;
+      default:
+        return this._defaultEventTarget;
+    }
+  }
+}
+
+class PPAPIInstance {
+  constructor(id, rt, info, window, eventHandler, containerWindow, mm) {
+    this.id = id;
+    this.rt = rt;
+    this.info = info;
+    this.window = window;
+    this.eventHandler = eventHandler;
+    this.containerWindow = containerWindow;
+    this.mm = mm;
+    this.eventHandlers = 0;
+    this.filteringEventHandlers = 0;
+    this.throttled_ = false;
+    this.cachedImageData = null;
+    this.viewport = new PPAPIViewport(this);
+    this.selectedText = "";
+
+    this.mm.addMessageListener("ppapi.js:fullscreenchange", (evt) => {
+      this.viewport.notify({
+        type: "fullscreenChange",
+        fullscreen: evt.data.fullscreen
+      });
+    });
+  }
+
+  bindGraphics(graphicsDevice) {
+    if (graphicsDevice) {
+      let canvas = graphicsDevice.canvas;
+
+      // FIXME This size should be adjusted according to devicePixelRatio.
+      canvas.style.width = canvas.width;
+      canvas.style.height = canvas.height;
+
+      // Attach the canvas of this Graphics object to DOM for displaying.
+      this.viewport.addView(canvas);
+    } else {
+      // Unbind all graphics objects, which means remove all canvas elements from DOM.
+      this.viewport.clearView();
+    }
+  }
+  handleEvent(event) {
+    dump(`EVENT ${event.type}\n`);
+    if ((event.type == "keydown" || event.type == "keyup") &&
+        event.keyCode == 224) {
+      return;
+    }
+    if (event.type == "keypress" && event.charCode === 0) {
+      return;
+    }
+
+    // To avoid cursor misalignment, we regenerate the mouse event which
+    // position is based on coordinate (0, 0) of viewport
+    let eventType = EventTypes.get(event.type);
+    let resource;
+    if (event instanceof this.window.MouseEvent) {
+      let rect = this.boundingRect;
+      let mouseEventInit = {
+        altkey: event.altkey,
+        button: event.button,
+        clientX: event.clientX - rect.left,
+        clientY: event.clientY - rect.top,
+        ctrlKey: event.ctrlKey,
+        detail: event.detail,
+        metaKey: event.metaKey,
+        movementX: event.movementX - rect.left,
+        movementY: event.movementY - rect.top,
+        shiftKey: event.shiftKey,
+      };
+      let offset_evt = new this.window.MouseEvent(event.type, mouseEventInit);
+      resource = new eventType.resourceCtor(this, offset_evt);
+      resource.timeStamp = event.timeStamp;
+    } else {
+      resource = new eventType.resourceCtor(this, event);
+    }
+
+    // We should only use sync call for filtering events
+    if (this.filteringEventHandlers & eventType.eventClass) {
+      let handled = this.rt.call(new InterfaceMemberCall("PPP_InputEvent;0.1", "HandleInputEvent", { instance: this, input_event: resource }), true);
+      if (handled) {
+        event.stopPropagation();
+      } else {
+        // FIXME Retarget at frameElement!
+      }
+    } else {
+      this.rt.call(new InterfaceMemberCall("PPP_InputEvent;0.1", "HandleInputEvent", { instance: this, input_event: resource }));
+    }
+
+    if (event.type == "compositionend") {
+      resource = new eventType.resourceCtor(this, event);
+      resource.eventType = PP_InputEvent_Type.PP_INPUTEVENT_TYPE_IME_TEXT;
+      if (this.filteringEventHandlers & eventType.eventClass) {
+        throw new Error("Add code for filtering for PP_INPUTEVENT_TYPE_IME_TEXT.");
+      }
+      this.rt.call(new InterfaceMemberCall("PPP_InputEvent;0.1", "HandleInputEvent", { instance: this, input_event: resource }));
+    }
+  }
+  registerEventHandler(eventClasses, filtering) {
+    let handler = this.handleEvent.bind(this);
+    let target = this.eventHandler;
+    let registeredHandlers = this.eventHandlers | this.filteringEventHandlers;
+    EventTypesByClass.forEach((domEvents, eventClass) => {
+      if ((eventClasses & eventClass) && !(registeredHandlers & eventClass)) {
+        domEvents.forEach((domEvent) => {
+          if (domEvent == "text") {
+            Cc["@mozilla.org/eventlistenerservice;1"].getService(Ci.nsIEventListenerService).
+            addSystemEventListener(target, "text", handler, true);
+          } else {
+            this.viewport.bindUIEvent(domEvent, handler);
+          }
+        });
+      }
+    });
+
+    if (filtering) {
+      this.eventHandlers &= ~eventClasses;
+      this.filteringEventHandlers |= eventClasses;
+    } else {
+      this.eventHandlers |= eventClasses;
+      this.filteringEventHandlers &= ~eventClasses;
+    }
+    return PP_OK;
+  }
+  get throttled() {
+    return this.throttled_;
+  }
+  set throttled(on) {
+    if (this.throttled_ != on) {
+      this.throttled_ = on;
+      let view = new View(this);
+      let call = new InterfaceMemberCall("PPP_Instance;1.1", "DidChangeView", { instance: this, view });
+      this.rt.call(call, true);
+    }
+  }
+  get boundingRect() {
+    if (!this.boundingRect_) {
+      this.boundingRect_ = this.viewport.getBoundingClientRect();
+    }
+    return this.boundingRect_;
+  }
+
+  toJSON() {
+    return this.id;
+  }
+
+  viewportActionHandler(message) {
+    switch(message.type) {
+      case 'setFullscreen':
+        this.mm.sendAsyncMessage("ppapi.js:setFullscreen", message.fullscreen);
+        break;
+      case 'viewport':
+      case 'rotateClockwise':
+      case 'rotateCounterclockwise':
+      case 'selectAll':
+      case 'getSelectedText':
+      case 'getNamedDestination':
+      case 'getPasswordComplete':
+        let data = PP_Var.fromJSValue(new Dictionary(message), this);
+        this.rt.call(new InterfaceMemberCall("PPP_Messaging;1.0", "HandleMessage",
+          { instance: this, var: data }));
+        break;
+      default:
+        throw new Error(`Invalid message type "${message.type}".`);
+    }
+  }
+  selectFindResult(isForward) {
+    let forward = Bool_PP_Var.convertValue(isForward);
+    this.rt.call(new InterfaceMemberCall("PPP_Find_Private;0.3", "SelectFindResult",
+      { instance: this, forward }));
+  }
+  startFind(term, isCaseSensitive) {
+    let case_sensitive = Bool_PP_Var.convertValue(isCaseSensitive);
+    this.rt.call(new InterfaceMemberCall("PPP_Find_Private;0.3", "StartFind",
+      { instance: this, text: term, case_sensitive }));
+  }
+  stopFind() {
+    this.rt.call(new InterfaceMemberCall("PPP_Find_Private;0.3", "StopFind",
+      { instance: this }));
+  }
+  didChangeFocus() {
+    if (this.focusChangeTimeout) {
+      this.window.clearTimeout(this.focusChangeTimeout);
+    }
+    // we use setTimeout to hold a small amount of time to
+    // make sure the focus state is stable.
+    this.focusChangeTimeout = this.window.setTimeout(() => {
+      let focusState = PP_Bool.PP_FALSE;
+      if (this.window.document.hasFocus() &&
+          (this.viewport.is(this.window.document.activeElement) ||
+          this.window.document.activeElement == this.window.document.getElementById('IMEInput'))) {
+        focusState = PP_Bool.PP_TRUE;
+      }
+      if (this.focusState != focusState) {
+        this.focusState = focusState;
+        let call = new InterfaceMemberCall("PPP_Instance;1.1", "DidChangeFocus", { instance: this, has_focus: focusState });
+        this.rt.call(call, true);
+      }
+    });
+  }
+  copyString() {
+    // Prevent user from calling copy without selecting any text and rewrite
+    // an empty string into clipboard.
+    if (!this.selectedText) {
+      return;
+    }
+
+    let clipboardHelper =
+      Components.classes["@mozilla.org/widget/clipboardhelper;1"].
+      getService(Components.interfaces.nsIClipboardHelper);
+    clipboardHelper.copyString(this.selectedText);
+  }
+}
+
+function PPAPIRuntime(process) {
+  this.instances = [];
+  this.process = process;
+  this.urlParser = Cc["@mozilla.org/network/url-parser;1?auth=maybe"]
+                   .getService(Ci.nsIURLParser);
+}
+
+PPAPIRuntime.prototype = {
+  get callback() {
+    return this.handler.bind(this);
+  },
+  handler: function(json) {
+    let obj;
+    try {
+      obj = JSON.parse(json);
+    } catch (e) {
+      dump(e.message + "\n");
+      let result = e.message.match(/line (\d+) column (\d+)/);
+      if (result) {
+        dump("  " + json + "\n");
+        dump("  " + new Array(parseInt(result[2], 10)).join("-") + "^\n");
+      }
+    }
+    if (!obj || !obj.__interface || !obj.__version || !obj.__method) {
+      dump("Invalid JSON RPC call: " + json + "\n");
+      return null;
+    }
+    let fn = obj.__interface + "_" + obj.__method;
+    let f = this.table[fn];
+    if (!f) {
+      dump(
+`Not implemented: ${json}
+    /**
+     *
+     */
+    ${obj.__interface}_${obj.__method}: function(json) {
+    },
+
+`);
+      return null;
+    }
+    let result = f.call(this, obj);
+    if (typeof result == 'undefined') {
+      return null;
+    }
+    return JSON.stringify([result]);
+  },
+
+  toPP_Var: function(v, instance) {
+    return PP_Var.fromJSValue(v, instance);
+  },
+
+  parseURL: function(url) {
+    let schemePos = {}, schemeLen = {};
+    let authorityPos = {}, authorityLen = {};
+    let pathPos = {}, pathLen = {};
+    this.urlParser.parseURL(url, url.length, schemePos, schemeLen, authorityPos,
+                            authorityLen, pathPos, pathLen);
+    let usernamePos = {}, usernameLen = {};
+    let passwordPos = {}, passwordLen = {};
+    let hostnamePos = {}, hostnameLen = {};
+    let port = {};
+    this.urlParser.parseAuthority(url.substr(authorityPos.value, authorityLen.value),
+                                  authorityLen.value, usernamePos, usernameLen,
+                                  passwordPos, passwordLen, hostnamePos, hostnameLen,
+                                  port);
+    let portPos = port < 0 ? 0 : hostnamePos.value + hostnameLen.value + 1;
+    let portLen = port < 0 ? -1 : pathPos.value - portPos;
+    let filepathPos = {}, filepathLen = {};
+    let queryPos = {}, queryLen = {};
+    let refPos = {}, refLen = {};
+    this.urlParser.parsePath(url.substr(pathPos.value, pathLen.value),
+                             pathLen.value, filepathPos, filepathLen, queryPos,
+                             queryLen, refPos, refLen);
+    return {
+      scheme: { begin: Math.min(0, schemePos.value), len: schemeLen.value },
+      username: { begin: Math.min(0, usernamePos.value), len: usernameLen.value },
+      password: { begin: Math.min(0, passwordPos.value), len: passwordLen.value },
+      host: { begin: Math.min(0, hostnamePos.value), len: hostnameLen.value },
+      port: { begin: portPos, len: portLen },
+      path: { begin: Math.min(0, filepathPos.value), len: filepathLen.value },
+      query: { begin: Math.min(0, queryPos.value), len: queryLen.value },
+      ref: { begin: Math.min(0, refPos.value), len: refLen.value },
+    };
+  },
+
+  allocateCachedBuffer: function(size) {
+    return this.process.allocateCachedBuffer(size);
+  },
+  getCachedBuffer: function(ptr) {
+    return this.process.getCachedBuffer(ptr);
+  },
+  freeCachedBuffer: function(ptr) {
+    this.process.freeCachedBuffer(ptr);
+  },
+  setBuffer: function(dest, source, size=source.byteLength) {
+    return this.process.setBuffer(source, size, dest);
+  },
+  copyBuffer: function(ptr, size) {
+    return this.process.copyFromBuffer(ptr, size);
+  },
+
+  get moduleLocalFiles() {
+    if (!("_moduleLocalFiles" in this)) {
+      this._moduleLocalFiles = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+      this._moduleLocalFiles.initWithPath(Services.cpmm.sendRpcMessage("ppapiflash.js:getModuleLocalFilesPath"));
+    }
+    return this._moduleLocalFiles;
+  },
+
+  createInstance: function(instanceInfo, window, eventHandler, containerWindow, mm) {
+    let i = 0;
+    for (; i < this.instances.length; ++i) {
+      if (!(i in this.instances)) {
+        break;
+      }
+    }
+
+    let instance = this.instances[i] = new PPAPIInstance(i, this, instanceInfo, window, eventHandler, containerWindow, mm);
+
+    let didChangeView = this.didChangeView.bind(this, instance);
+    instance.viewport.bindUIEvent("MozScrolledAreaChanged", didChangeView);
+    instance.viewport.bindUIEvent("fullscreenchange", didChangeView);
+    instance.viewport.bindUIEvent("resize", didChangeView);
+    instance.viewport.bindUIEvent("blur", instance.didChangeFocus.bind(instance));
+    instance.viewport.bindUIEvent("focus", instance.didChangeFocus.bind(instance));
+
+    let argn = instanceInfo.arguments.keys;
+    let argv = instanceInfo.arguments.values;
+
+    this.call(new InterfaceMemberCall("PPP_Instance;1.1", "DidCreate", { instance: i, argc: argn.length, argn: argn, argv: argv }), true);
+    if (instanceInfo.setupJSInstanceObject) {
+      let jsObj = this.call(new InterfaceMemberCall("PPP_Instance_Private;0.1", "GetInstanceObject", { instance: i }), true);
+      if (PP_VarType[jsObj.type] == PP_VarType.PP_VARTYPE_OBJECT) {
+        instance.mm.sendRpcMessage("ppapiflash.js:setInstancePrototype", undefined, { proto: Object_PP_Var.getAsJSValue(jsObj) });
+      }
+    }
+
+    if (instanceInfo.isFullFrame) {
+      let loader = new URLLoader(instance);
+      loader.openURL("GET", instanceInfo.url,
+                     (result) => { this.call(new InterfaceMemberCall("PPP_Instance;1.1", "HandleDocumentLoad", { instance, url_loader: loader })); });
+    }
+    this.didChangeView(instance);
+    return i;
+  },
+  destroyInstances: function() {
+    for (let i of this.instances) {
+      this.destroyInstance(i, false);
+    }
+    this.instances.length = 0;
+  },
+  destroyInstance: function(instance, truncateInstancesArray=true) {
+    this.call(new InterfaceMemberCall("PPP_Instance;1.1", "DidDestroy", { instance: instance }), true);
+    delete this.instances[instance];
+    if (truncateInstancesArray) {
+      let i = this.instances.length;
+      while (--i >= 0) {
+        if (i in this.instances) {
+          break;
+        }
+      }
+      this.instances.length = i + 1;
+    }
+  },
+  hasInstances: function() {
+    return this.instances.some(() => true);
+  },
+  didChangeView: function(instance) {
+    // In case of |getBoundingClientRect()| will reflush the layout and cause
+    // bad performance, we cache the bounding rectangle for frequently
+    // regenerating the offset mouse event.
+    instance.boundingRect_ = instance.viewport.getBoundingClientRect();
+
+    let view = new View(instance);
+    let call = new InterfaceMemberCall("PPP_Instance;1.1", "DidChangeView", { instance, view });
+    this.call(call, true);
+  },
+
+  call: function(call, sync=false) {
+    if (sync) {
+dump(`callFromJSON: > ${JSON.stringify(call)}\n`);
+      let result = this.process.sendMessage(JSON.stringify(call));
+dump(`callFromJSON: < ${JSON.stringify(call)}\n`);
+      return result ? JSON.parse(result) : result;
+    }
+
+    let thread = Services.tm.currentThread;
+    thread.dispatch(() => {
+dump(`callFromJSON (async): > ${JSON.stringify(call)}\n`);
+      let result = this.process.sendMessage(JSON.stringify(call));
+dump(`callFromJSON: < ${JSON.stringify(call)}\n`);
+    }, Ci.nsIThread.DISPATCH_NORMAL);
+  },
+
+  table: {
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance,
+     *     [in] PP_Resource config,
+     *     [in] PPB_Audio_Callback audio_callback,
+     *     [inout] mem_t user_data);
+     *    */
+    PPB_Audio_Create: function(json) {
+      let instance = this.instances[json.instance];
+      let config = PP_Resource.lookup(json.config);
+      return new Audio(instance, config.bufferSize, config.frameCount,
+                       json.audio_callback, json.user_data);
+    },
+
+    /**
+     * PP_Bool StartPlayback(
+     *     [in] PP_Resource audio);
+     */
+    PPB_Audio_StartPlayback: function(json) {
+      let audio = PP_Resource.lookup(json.audio);
+      audio.start();
+      return PP_Bool.PP_TRUE;
+    },
+
+    /**
+     * PP_Bool StopPlayback(
+     *     [in] PP_Resource audio);
+     */
+    PPB_Audio_StopPlayback: function(json) {
+      let audio = PP_Resource.lookup(json.audio);
+      audio.stop();
+      return PP_Bool.PP_TRUE;
+    },
+
+    /**
+     * PP_Resource CreateStereo16Bit(
+     *     [in] PP_Instance instance,
+     *     [in] PP_AudioSampleRate sample_rate,
+     *     [in] uint32_t sample_frame_count);
+     */
+    PPB_AudioConfig_CreateStereo16Bit: function(json) {
+      return new AudioConfig(this.instances[json.instance],
+                             json.sample_frame_count);
+    },
+
+    /**
+     * uint32_t RecommendSampleFrameCount(
+     *     [in] PP_Instance instance,
+     *     [in] PP_AudioSampleRate sample_rate,
+     *     [in] uint32_t requested_sample_frame_count);
+     */
+    PPB_AudioConfig_RecommendSampleFrameCount: function(json) {
+      let rate = Math.pow(2, Math.ceil(Math.log2(json.sample_rate)));
+      return Math.max(256, Math.min(rate, 16384));
+    },
+
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance);
+     */
+    PPB_AudioInput_Dev_Create: function(json) {
+      return 0;
+    },
+
+    /**
+     * int32_t EnumerateDevices(
+     *     [in] PP_Resource audio_input,
+     *     [in] PP_ArrayOutput output,
+     *     [in] PP_CompletionCallback callback);
+     */
+    PPB_AudioInput_Dev_EnumerateDevices: function(json) {
+      return PP_ERROR_BADRESOURCE;
+    },
+
+
+    /**
+     * PP_Var GetFontFamilies(
+     *     [in] PP_Instance instance);
+     */
+    PPB_BrowserFont_Trusted_GetFontFamilies: function(json) {
+      let instance = this.instances[json.instance];
+      let enumerator = Cc["@mozilla.org/gfx/fontenumerator;1"].createInstance(Ci.nsIFontEnumerator);
+      return new String_PP_Var(enumerator.EnumerateAllFonts({}).join('\0'), instance);
+    },
+
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance,
+     *     [in] PP_BrowserFont_Trusted_Description description);
+     */
+    PPB_BrowserFont_Trusted_Create: function(json) {
+      return new BrowserFont_Trusted(this.instances[json.instance],
+                                     json.description);
+    },
+
+    /**
+     * PP_Bool Describe(
+     *     [in] PP_Resource font,
+     *     [out] PP_BrowserFont_Trusted_Description description,
+     *     [out] PP_BrowserFont_Trusted_Metrics metrics);
+     */
+    PPB_BrowserFont_Trusted_Describe: function(json) {
+      let font = PP_Resource.lookup(json.font);
+      let description = Object.assign({}, font.description);
+      if (font.customFamily) {
+        description.face = new String_PP_Var(font.customFamily);
+      } else {
+        description.face = new Undefined_PP_Var();
+      }
+      description.family = PP_BrowserFont_Trusted_Family[description.family];
+      description.weight = PP_BrowserFont_Trusted_Weight[description.weight];
+      description.italic = PP_Bool[description.italic];
+      description.small_caps = PP_Bool[description.small_caps];
+      return [PP_Bool.PP_TRUE, { description: description, metrics: { height: 12, ascent: 12, descent: 12, line_spacing: 12, x_height: 12 } }];
+    },
+
+    /**
+     * PP_Bool DrawTextAt(
+     *     [in] PP_Resource font,
+     *     [in] PP_Resource image_data,
+     *     [in] PP_BrowserFont_Trusted_TextRun text,
+     *     [in] PP_Point position,
+     *     [in] uint32_t color,
+     *     [in] PP_Rect clip,
+     *     [in] PP_Bool image_data_is_opaque);
+     */
+    PPB_BrowserFont_Trusted_DrawTextAt: function(json) {
+      let font = PP_Resource.lookup(json.font);
+      let imageData = PP_Resource.lookup(json.image_data);
+      let textType = PP_VarType[json.text.text.type];
+      let text;
+      if (textType == PP_VarType.PP_VARTYPE_UNDEFINED ||
+          textType == PP_VarType.PP_VARTYPE_NULL) {
+        text = "";
+      } else {
+        text = String_PP_Var.getAsJSValue(json.text.text);
+      }
+      let context = imageData.beginDrawing();
+      context.font = font.fontRule;
+      let r = ((json.color & (0xff << 0)) >>> 0);
+      let g = ((json.color & (0xff << 8)) >>> 8);
+      let b = ((json.color & (0xff << 16)) >>> 16);
+      let a = ((json.color & (0xff << 24)) >>> 24) / 255;
+      // It seems the color matches its ImageData format.
+      context.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`;
+      let { x, y } = json.position;
+      let { point: { x: clipX, y: clipY }, size: { width: clipW, height: clipH } } = json.clip;
+      context.rect(clipX, clipY, clipW, clipH);
+      context.clip();
+      context.fillText(text, x, y);
+      //dump(context.canvas.toDataURL() + "\n");
+      imageData.endDrawing();
+      return PP_Bool.PP_TRUE;
+    },
+
+    /**
+     * int32_t MeasureText(
+     *     [in] PP_Resource font,
+     *     [in] PP_TextRun_Dev text);
+     */
+    PPB_BrowserFont_Trusted_MeasureText: function(json) {
+      let font = PP_Resource.lookup(json.font);
+      let textType = PP_VarType[json.text.text.type];
+      let text;
+      if (textType == PP_VarType.PP_VARTYPE_UNDEFINED ||
+          textType == PP_VarType.PP_VARTYPE_NULL) {
+        text = "";
+      } else {
+        text = String_PP_Var.getAsJSValue(json.text.text);
+      }
+      return font.measureText(text);
+    },
+
+
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance,
+     *     [in] uint32_t size_in_bytes);
+     */
+    PPB_Buffer_Dev_Create: function(json) {
+      return new Buffer(this.instances[json.instance],
+                        json.size_in_bytes);
+    },
+
+    /**
+     * PP_Bool Describe(
+     *     [in] PP_Resource resource,
+     *     [out] uint32_t size_in_bytes);
+     */
+    PPB_Buffer_Dev_Describe: function(json) {
+      let buffer = PP_Resource.lookup(json.resource);
+      return [PP_Bool.PP_TRUE, { size_in_bytes: buffer.size }];
+    },
+
+    /**
+     * mem_t Map(
+     *     [in] PP_Resource resource);
+     */
+    PPB_Buffer_Dev_Map: function(json) {
+      let buffer = PP_Resource.lookup(json.resource);
+      return buffer.map();
+    },
+
+    /**
+     * void Unmap(
+     *     [in] PP_Resource resource);
+     */
+    PPB_Buffer_Dev_Unmap: function(json) {
+      let buffer = PP_Resource.lookup(json.resource);
+      buffer.unmap();
+    },
+
+
+    /**
+     * PP_Var GetDefaultCharSet([in] PP_Instance instance);
+     */
+    PPB_CharSet_Dev_GetDefaultCharSet: function(json) {
+      return new String_PP_Var("utf-8");
+    },
+
+    /**
+     * str_t UTF16ToCharSet([in] PP_Instance instance,
+     *                      [in, size_as=utf16_len] uint16_t[] utf16,
+     *                      [in] uint32_t utf16_len,
+     *                      [in] str_t output_char_set,
+     *                      [in] PP_CharSet_ConversionError on_error,
+     *                      [out] uint32_t output_length);
+     */
+    PPB_CharSet_Dev_UTF16ToCharSet: function(json) {
+      let instance = this.instances[json.instance];
+      let utf16 = Uint16Array.from(json.utf16);
+      let decoded = new TextDecoder("utf-16").decode(utf16);
+      let converted = new TextEncoder(json.output_char_set).encode(decoded);
+      return [Array.from(new Uint8Array(converted)), { output_length: converted.buffer.byteLength }];
+    },
+
+    /**
+     * uint16_ptr_t CharSetToUTF16([in] PP_Instance instance,
+     *                             [in] str_t input,
+     *                             [in] uint32_t input_len,
+     *                             [in] str_t input_char_set,
+     *                             [in] PP_CharSet_ConversionError on_error,
+     *                             [out] uint32_t output_utf16_length);
+     */
+    PPB_CharSet_Dev_CharSetToUTF16: function(json) {
+      let instance = this.instances[json.instance];
+      let input = Uint8ClampedArray.from(json.input);
+      let decoded = new TextDecoder(json.input_char_set).decode(input);
+      let converted = new TextEncoder("utf-16").encode(decoded);
+      return [Array.from(new Uint16Array(converted.buffer)), { output_utf16_length: converted.byteLength / 2 }];
+    },
+
+
+    /**
+     * void Log(
+     *     [in] PP_Instance instance,
+     *     [in] PP_LogLevel level,
+     *     [in] PP_Var value);
+     */
+    PPB_Console_Log: function(json) {
+      let instance = this.instances[json.instance];
+      dump("FROM FLASH: " + String_PP_Var.getAsJSValue(json.value) + "\n");
+      instance.mm.sendRpcMessage("ppapiflash.js:log", String_PP_Var.getAsJSValue(json.value));
+    },
+
+
+    /**
+     * void AddRefResource([in] PP_Resource resource);
+     */
+    PPB_Core_AddRefResource: function(json) {
+      PP_Resource.lookup(json.resource).addRef();
+    },
+
+    /**
+     * void ReleaseResource([in] PP_Resource resource);
+     */
+    PPB_Core_ReleaseResource: function(json) {
+      PP_Resource.lookup(json.resource).release();
+    },
+
+    /**
+     * PP_Time GetTime();
+     */
+    PPB_Core_GetTime: function(json) {
+      return Date.now() / 1000;
+    },
+
+    /**
+     * void CallOnMainThread(
+     *     [in] int32_t delay_in_milliseconds,
+     *     [in] PP_CompletionCallback callback,
+     *     [in] int32_t result);
+     */
+    PPB_Core_CallOnMainThread: function(json) {
+      let callback = new CallbackCall("PP_CompletionCallback", json.callback, { result: json.result });
+      if (json.delay_in_milliseconds > 0) {
+        // It'd be better if we had the instance so we could use setTimeout on
+        // the window.
+        let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+        timer.initWithCallback(() => { this.call(callback); },
+                               json.delay_in_milliseconds,
+                               Ci.nsITimer.TYPE_ONE_SHOT);
+      } else {
+        this.call(callback);
+      }
+    },
+
+    /**
+     * PP_Bool IsMainThread();
+    PPB_Core_IsMainThread: function(json, mainThread) {
+      return mainThread ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE;
+    },
+     */
+
+    /**
+     * void GetRandomBytes([out] str_t buffer, [in] uint32_t num_bytes);
+     */
+    PPB_Crypto_Dev_GetRandomBytes: function(json) {
+      let bytes = Services.cpmm.sendRpcMessage("ppapi.js:generateRandomBytes", json.num_bytes);
+      return [{ buffer: bytes }];
+    },
+
+    /**
+     * PP_Bool SetCursor([in] PP_Instance instance,
+     *                   [in] PP_CursorType_Dev type,
+     *                   [in] PP_Resource custom_image,
+     *                   [in] PP_Point hot_spot);
+     */
+    PPB_CursorControl_Dev_SetCursor: function(json) {
+      let instance = this.instances[json.instance];
+      let cursor;
+      switch (PP_CursorType_Dev[json.type]) {
+        case PP_CursorType_Dev.PP_CURSORTYPE_CUSTOM:
+          throw new Error("Custom cursors not implemented.");
+        case PP_CursorType_Dev.PP_CURSORTYPE_POINTER:
+          cursor = "default";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_CROSS:
+          cursor = "crosshair";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_HAND:
+          cursor = "pointer";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_IBEAM:
+          cursor = "text";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_WAIT:
+          cursor = "wait";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_HELP:
+          cursor = "help";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_EASTRESIZE:
+          cursor = "e-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_NORTHRESIZE:
+          cursor = "n-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_NORTHEASTRESIZE:
+          cursor = "ne-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_NORTHWESTRESIZE:
+          cursor = "nw-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_SOUTHRESIZE:
+          cursor = "s-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_SOUTHEASTRESIZE:
+          cursor = "se-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_SOUTHWESTRESIZE:
+          cursor = "sw-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_WESTRESIZE:
+          cursor = "w-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_NORTHSOUTHRESIZE:
+          cursor = "ns-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_EASTWESTRESIZE:
+          cursor = "ew-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_NORTHEASTSOUTHWESTRESIZE:
+          cursor = "nesw-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_NORTHWESTSOUTHEASTRESIZE:
+          cursor = "nwse-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_COLUMNRESIZE:
+          cursor = "col-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_ROWRESIZE:
+          cursor = "row-resize";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_MIDDLEPANNING:
+        case PP_CursorType_Dev.PP_CURSORTYPE_EASTPANNING:
+        case PP_CursorType_Dev.PP_CURSORTYPE_NORTHPANNING:
+        case PP_CursorType_Dev.PP_CURSORTYPE_NORTHEASTPANNING:
+        case PP_CursorType_Dev.PP_CURSORTYPE_NORTHWESTPANNING:
+        case PP_CursorType_Dev.PP_CURSORTYPE_SOUTHPANNING:
+        case PP_CursorType_Dev.PP_CURSORTYPE_SOUTHEASTPANNING:
+        case PP_CursorType_Dev.PP_CURSORTYPE_SOUTHWESTPANNING:
+        case PP_CursorType_Dev.PP_CURSORTYPE_WESTPANNING:
+          throw new Error("Panning cursors not implemented.");
+        case PP_CursorType_Dev.PP_CURSORTYPE_MOVE:
+          cursor = "move";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_VERTICALTEXT:
+          cursor = "vertical-text";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_CELL:
+          cursor = "cell";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_CONTEXTMENU:
+          cursor = "context-menu";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_ALIAS:
+          cursor = "alias";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_PROGRESS:
+          cursor = "progress";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_NODROP:
+          cursor = "no-drop";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_COPY:
+          cursor = "copy";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_NONE:
+          cursor = "none";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_NOTALLOWED:
+          cursor = "not-allowed";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_ZOOMIN:
+          cursor = "zoom-in";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_ZOOMOUT:
+          cursor = "zoom-out";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_GRAB:
+          cursor = "grab";
+          break;
+        case PP_CursorType_Dev.PP_CURSORTYPE_GRABBING:
+          cursor = "grabbing";
+          break;
+      }
+      instance.viewport.setCursor(cursor);
+      return PP_Bool.PP_TRUE;
+    },
+
+
+    /**
+     * void NumberOfFindResultsChanged(
+     *     [in] PP_Instance instance,
+     *     [in] int32_t total,
+     *     [in] PP_Bool final_result);
+     */
+    PPB_Find_Private_NumberOfFindResultsChanged: function(json) {
+      let instance = this.instances[json.instance];
+      instance.viewport.notify({
+        type: "numberOfFindResultsChanged",
+        total: json.total,
+        finalResult: (PP_Bool[json.final_result] == PP_Bool.PP_TRUE),
+      });
+    },
+
+    /**
+     * void SelectedFindResultChanged(
+     *     [in] PP_Instance instance,
+     *     [in] int32_t index);
+     */
+    PPB_Find_Private_SelectedFindResultChanged: function(json) {
+      let instance = this.instances[json.instance];
+      instance.viewport.notify({
+        type: "selectedFindResultChanged",
+        index: json.index,
+      });
+    },
+
+    /**
+     * void SetTickmarks(
+     *     [in] PP_Instance instance,
+     *     [in, size_as=count] PP_Rect[] tickmarks,
+     *     [in] uint32_t count);
+     */
+    PPB_Find_Private_SetTickmarks: function(json) {
+      let instance = this.instances[json.instance];
+      instance.viewport.notify({
+        type: "setTickmarks",
+        tickmarks: json.tickmarks,
+        count: json.count,
+      });
+    },
+
+
+    /**
+     * PP_Bool IsFormatAvailable(
+     *     [in] PP_Instance instance_id,
+     *     [in] PP_Flash_Clipboard_Type clipboard_type,
+     *     [in] uint32_t format);
+     */
+    PPB_Flash_Clipboard_IsFormatAvailable: function(json) {
+      //FIXME We only support standard clipboard w/ plaintext format
+      if (PP_Flash_Clipboard_Format[json.clipboard_type] ==
+          PP_Flash_Clipboard_Format.PP_FLASH_CLIPBOARD_TYPE_STANDARD) {
+        if (json.format ==
+            PP_Flash_Clipboard_Format.PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT) {
+          return PP_Bool.PP_TRUE;
+        }
+      }
+      return PP_Bool.PP_FALSE;
+    },
+
+    /**
+     * PP_Var ReadData([in] PP_Instance instance_id,
+     *     [in] PP_Flash_Clipboard_Type clipboard_type,
+     *     [in] uint32_t format);
+     */
+    PPB_Flash_Clipboard_ReadData: function(json) {
+      //FIXME We only support standard clipboard w/ plaintext format
+      if (PP_Flash_Clipboard_Format[json.clipboard_type] ==
+          PP_Flash_Clipboard_Format.PP_FLASH_CLIPBOARD_TYPE_STANDARD) {
+        if (json.format ==
+            PP_Flash_Clipboard_Format.PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT) {
+          let trans = Cc["@mozilla.org/widget/transferable;1"]
+            .createInstance(Ci.nsITransferable);
+          trans.init(null)
+          trans.addDataFlavor("text/unicode");
+          Services.clipboard.getData(
+            trans, Services.clipboard.kGlobalClipboard);
+          let str = {};
+          let strLength = {};
+          trans.getTransferData("text/unicode", str, strLength);
+          let pasteText = str.value.QueryInterface(Ci.nsISupportsString).data;
+          return new String_PP_Var(pasteText);
+        }
+      }
+      return new PP_Var();
+    },
+
+    /**
+     * int32_t WriteData(
+     *     [in] PP_Instance instance_id,
+     *     [in] PP_Flash_Clipboard_Type clipboard_type,
+     *     [in] uint32_t data_item_count,
+     *     [in, size_is(data_item_count)] uint32_t[] formats,
+     *     [in, size_is(data_item_count)] PP_Var[] data_items);
+     */
+    PPB_Flash_Clipboard_WriteData: function(json) {
+      //FIXME We only support standard clipboard w/ plaintext format
+      let clipboardHelper =
+        Components.classes["@mozilla.org/widget/clipboardhelper;1"].
+        getService(Components.interfaces.nsIClipboardHelper);
+      for (let i = 0; i < json.data_item_count; ++i) {
+        if (json.formats[i] ==
+            PP_Flash_Clipboard_Format.PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT) {
+          clipboardHelper.copyString(
+            String_PP_Var.getAsJSValue(json.data_items[i]));
+        } else {
+          clipboardHelper.copyString("");
+          return PP_ERROR_BADARGUMENT;
+        }
+      }
+      return PP_OK;
+    },
+
+
+    /**
+     * void SetInstanceAlwaysOnTop(
+     *     [in] PP_Instance instance,
+     *     [in] PP_Bool on_top);
+     */
+    PPB_Flash_SetInstanceAlwaysOnTop: function(json) {
+    },
+
+    /**
+     * double_t GetLocalTimeZoneOffset(
+     *     [in] PP_Instance instance,
+     *     [in] PP_Time t);
+     */
+    PPB_Flash_GetLocalTimeZoneOffset: function(json) {
+      return (new Date(json.t)).getTimezoneOffset() * 60;
+    },
+
+    /**
+     * PP_Var GetCommandLineArgs(
+     *     [in] PP_Module module);
+     */
+    PPB_Flash_GetCommandLineArgs: function(json) {
+      // "enable_trace_to_console=1 log_level=5"
+      return new String_PP_Var("enable_trace_to_console=1 log_level=5");
+    },
+
+    /**
+     * PP_Var GetSetting(PP_Instance instance, PP_FlashSetting setting);
+     */
+    PPB_Flash_GetSetting: function(json) {
+      switch (PP_FlashSetting[json.setting]) {
+        case PP_FlashSetting.PP_FLASHSETTING_3DENABLED:
+          return new Bool_PP_Var(true);
+        case PP_FlashSetting.PP_FLASHSETTING_INCOGNITO:
+          return new Bool_PP_Var(false);
+        case PP_FlashSetting.PP_FLASHSETTING_STAGE3DENABLED:
+          return new Bool_PP_Var(true);
+        case PP_FlashSetting.PP_FLASHSETTING_LANGUAGE:
+          return new String_PP_Var("en-US");
+        case PP_FlashSetting.PP_FLASHSETTING_NUMCORES:
+          return new Int32_PP_Var(4);
+        case PP_FlashSetting.PP_FLASHSETTING_LSORESTRICTIONS:
+          return new Int32_PP_Var(PP_FlashLSORestrictions.PP_FLASHLSORESTRICTIONS_IN_MEMORY);
+      }
+      return 0;
+    },
+
+    /**
+     * PP_Bool SetCrashData([in] PP_Instance instance,
+     *                      [in] PP_FlashCrashKey key,
+     *                      [in] PP_Var value);
+     */
+    PPB_Flash_SetCrashData: function(json) {
+      return PP_Bool.PP_TRUE;
+    },
+
+
+    /**
+     * int32_t OpenFile(
+     *     [in] PP_Instance instance,
+     *     [in] str_t path,
+     *     [in] int32_t mode,
+     *     [out] PP_FileHandle file);
+     */
+    PPB_Flash_File_ModuleLocal_OpenFile: function(json) {
+      let file = this.moduleLocalFiles.clone();
+      json.path.split("/").forEach((pathItem) => file.appendRelativePath(pathItem));
+
+      let flags;
+      if (!(json.mode & PP_FileOpenFlags.PP_FILEOPENFLAG_WRITE)) {
+        flags = PR_RDONLY;
+        if (json.mode & PP_FileOpenFlags.PP_FILEOPENFLAG_APPEND) {
+          flags |= PR_APPEND;
+        }
+      } else if (json.mode & PP_FileOpenFlags.PP_FILEOPENFLAG_READ) {
+        flags = PR_RDWR;
+      } else {
+        flags = PR_WRONLY;
+      }
+      if (json.mode & PP_FileOpenFlags.PP_FILEOPENFLAG_TRUNCATE) {
+        flags |= PR_TRUNCATE;
+      }
+      if (json.mode & PP_FileOpenFlags.PP_FILEOPENFLAG_CREATE) {
+        flags |= PR_CREATE_FILE;
+        if ((json.mode & PP_FileOpenFlags.PP_FILEOPENFLAG_EXCLUSIVE) && file.exists()) {
+          flags |= PR_EXCL;
+        }
+      }
+
+      try {
+        let handle = this.process.openAndSend(file, flags, 0o600);
+        return [PP_OK, { file: handle }];
+      } catch (e) {
+        return [PP_ERROR_FAILED, { file: null }];
+      }
+    },
+
+    /**
+     * int32_t RenameFile(
+     *     [in] PP_Instance instance,
+     *     [in] str_t path_from,
+     *     [in] str_t path_to);
+     */
+    PPB_Flash_File_ModuleLocal_RenameFile: function(json) {
+      let fromFile = this.moduleLocalFiles.clone();
+      json.path_from.split("/").forEach((pathItem) => fromFile.appendRelativePath(pathItem));
+      let toItems = json.path_to.split("/");
+      let newName = toItems.pop();
+      let toDir = this.moduleLocalFiles.clone();
+      toItems.forEach((pathItem) => toDir.appendRelativePath(pathItem));
+      try {
+        fromFile.renameTo(toDir, newName);
+        return PP_OK;
+      } catch (e) {
+        return PP_ERROR_FAILED;
+      }
+    },
+
+    /**
+     * int32_t DeleteFileOrDir(
+     *     [in] PP_Instance instance,
+     *     [in] str_t path,
+     *     [in] PP_Bool recursive);
+     */
+    PPB_Flash_File_ModuleLocal_DeleteFileOrDir: function(json) {
+      let file = this.moduleLocalFiles.clone();
+      json.path.split("/").forEach((pathItem) => file.appendRelativePath(pathItem));
+      if (file.exists()) {
+        try {
+          file.remove(PP_Bool[json.recursive] == PP_Bool.PP_TRUE);
+        } catch (e) {
+          dump(e.toSource() + "\n");
+          return PP_ERROR_FAILED;
+        }
+      }
+      return PP_OK;
+    },
+
+    /**
+     * int32_t CreateDir(
+     *     [in] PP_Instance instance,
+     *     [in] str_t path);
+     */
+    PPB_Flash_File_ModuleLocal_CreateDir: function(json) {
+      let directory = this.moduleLocalFiles.clone();
+      json.path.split("/").forEach((pathItem) => directory.appendRelativePath(pathItem));
+      try {
+        directory.create(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
+      } catch (e) {
+        if (e.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
+          return PP_ERROR_FAILED;
+        }
+      }
+      return PP_OK;
+    },
+
+    /**
+     * int32_t QueryFile(
+     *     [in] PP_Instance instance,
+     *     [in] str_t path,
+     *     [out] PP_FileInfo info);
+     */
+    PPB_Flash_File_ModuleLocal_QueryFile: function(json) {
+      let file = this.moduleLocalFiles.clone();
+      json.path.split("/").forEach((pathItem) => file.appendRelativePath(pathItem));
+      if (!file.exists()) {
+        return [PP_ERROR_FILENOTFOUND, { info: null }];
+      }
+
+      return [PP_OK, { info: {
+        size: file.fileSize,
+        type: file.isDirectory() ? PP_FileType.PP_FILETYPE_DIRECTORY : PP_FileType.PP_FILETYPE_REGULAR,
+        system_type: PP_FileSystemType.PP_FILESYSTEMTYPE_LOCALPERSISTENT,
+        creation_time: file.lastModifiedTime,
+        last_access_time: file.lastModifiedTime,
+        last_modified_time: file.lastModifiedTime
+      } }];
+    },
+
+    /**
+     * int32_t GetDirContents(
+     *     [in] PP_Instance instance,
+     *     [in] str_t path,
+     *     [out] PP_DirContents_Dev contents);
+     */
+    PPB_Flash_File_ModuleLocal_GetDirContents: function(json) {
+      let directory = this.moduleLocalFiles.clone();
+      json.path.split("/").forEach((pathItem) => directory.appendRelativePath(pathItem));
+      if (!directory.exists() || !directory.isDirectory()) {
+        return [PP_ERROR_FILENOTFOUND, { contents: null }];
+      }
+
+      let entries = [];
+      let enumerator = directory.directoryEntries;
+      while (enumerator.hasMoreElements()) {
+        let file = enumerator.getNext().QueryInterface(Ci.nsIFile);
+        entries.push({ name: file.leafName, is_dir: file.isDirectory() });
+      }
+      return [PP_OK, { contents: { count: entries.length, entries } }];
+    },
+
+    /**
+     * int32_t CreateTemporaryFile(
+     *     [in] PP_Instance instance,
+     *     [out] PP_FileHandle file);
+     */
+    PPB_Flash_File_ModuleLocal_CreateTemporaryFile: function(json) {
+      let handle = this.process.openAndSend(null, PR_RDWR, 0o600);
+      return [PP_OK, { file: handle }];
+    },
+
+
+    /**
+     * PP_Bool IsFullscreen(
+     *     [in] PP_Instance instance);
+     */
+    PPB_FlashFullscreen_IsFullscreen: function(json) {
+      let instance = this.instances[json.instance];
+      return instance.mm.sendRpcMessage("ppapi.js:isFullscreen")[0] ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE;
+    },
+
+    /**
+     * PP_Bool SetFullscreen(
+     *     [in] PP_Instance instance,
+     *     [in] PP_Bool fullscreen);
+     */
+    PPB_FlashFullscreen_SetFullscreen: function(json) {
+      let instance = this.instances[json.instance];
+      instance.mm.sendRpcMessage("ppapi.js:setFullscreen", json.fullScreen == PP_Bool.PP_TRUE);
+    },
+
+    /**
+     * PP_Bool GetScreenSize(
+     *     [in] PP_Instance instance,
+     *     [out] PP_Size size);
+     */
+    PPB_FlashFullscreen_GetScreenSize: function(json) {
+      let screen = this.instances[json.instance].window.screen;
+      return [PP_Bool.PP_FALSE, { size: { width: screen.width, height: screen.height } }];
+    },
+
+
+    /**
+     * PP_Resource Create([in] PP_Instance instance);
+     */
+    PPB_Flash_MessageLoop_Create: function(json) {
+      return new Flash_MessageLoop(this.instances[json.instance]);
+    },
+
+    /**
+     * int32_t Run([in] PP_Resource flash_message_loop);
+     */
+    PPB_Flash_MessageLoop_Run: function(json) {
+      let messageLoop = PP_Resource.lookup(json.flash_message_loop);
+      messageLoop.run();
+      return PP_OK;
+    },
+
+    /**
+     * void Quit([in] PP_Resource flash_message_loop);
+     */
+    PPB_Flash_MessageLoop_Quit: function(json) {
+      let messageLoop = PP_Resource.lookup(json.flash_message_loop);
+      messageLoop.quit();
+    },
+
+
+    /**
+     * PP_Var GetFontFamilies(
+     *     [in] PP_Instance instance);
+     */
+    PPB_Font_Dev_GetFontFamilies: function(json) {
+      let enumerator = Cc["@mozilla.org/gfx/fontenumerator;1"].createInstance(Ci.nsIFontEnumerator);
+      return new String_PP_Var(enumerator.EnumerateAllFonts({}).join('\0'));
+    },
+
+
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance,
+     *     [in] PP_Size size,
+     *     [in] PP_Bool is_always_opaque);
+     */
+    PPB_Graphics2D_Create: function(json) {
+      return new Graphics2D(this.instances[json.instance], json.size.width, json.size.height);
+    },
+
+    /**
+     * void PaintImageData(
+     *     [in] PP_Resource graphics_2d,
+     *     [in] PP_Resource image_data,
+     *     [in] PP_Point top_left,
+     *     [in] PP_Rect src_rect);
+     */
+    PPB_Graphics2D_PaintImageData: function(json) {
+      let graphics = PP_Resource.lookup(json.graphics_2d);
+      let imageData = PP_Resource.lookup(json.image_data);
+      let operation = new Graphics2DPaintOperation(imageData, json.top_left.x, json.top_left.y,
+          [ json.src_rect.point.x, json.src_rect.point.y, json.src_rect.size.width, json.src_rect.size.height ]);
+      graphics.addOperation(operation);
+    },
+
+    /**
+     * void ReplaceContents(
+     *     [in] PP_Resource graphics_2d,
+     *     [in] PP_Resource image_data);
+     */
+    PPB_Graphics2D_ReplaceContents: function(json) {
+      let graphics = PP_Resource.lookup(json.graphics_2d);
+      let imageData = PP_Resource.lookup(json.image_data);
+      let operation = new Graphics2DPaintOperation(imageData, 0, 0);
+      graphics.clearOperations();
+      graphics.addOperation(operation);
+    },
+
+    /**
+     * void Scroll(
+     *     [in] PP_Resource graphics_2d,
+     *     [in] PP_Rect clip_rect,
+     *     [in] PP_Point amount);
+     */
+    PPB_Graphics2D_Scroll: function(json) {
+      let graphics = PP_Resource.lookup(json.graphics_2d);
+      let operation = new Graphics2DScrollOperation([json.clip_rect.point.x, json.clip_rect.point.y,
+          json.clip_rect.size.width, json.clip_rect.size.height], json.amount.x, json.amount.y);
+      graphics.addOperation(operation);
+    },
+
+    /**
+     * int32_t Flush(
+     *     [in] PP_Resource graphics_2d,
+     *     [in] PP_CompletionCallback callback);
+     */
+    PPB_Graphics2D_Flush: function(json) {
+      let graphics = PP_Resource.lookup(json.graphics_2d);
+      return graphics.flush(json.callback);
+    },
+
+    /**
+     * PP_Bool SetScale(
+     *     [in] PP_Resource resource,
+     *     [in] float_t scale);
+     */
+    PPB_Graphics2D_SetScale: function(json) {
+      let graphics = PP_Resource.lookup(json.resource);
+      let context = graphics.context;
+      context.scale(json.scale, json.scale);
+      return PP_Bool.PP_TRUE;
+    },
+
+
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance,
+     *     [in] PP_Resource share_context,
+     *     [in] int32_t[] attrib_list);
+     */
+    PPB_Graphics3D_Create: function(json) {
+      let instance = this.instances[json.instance];
+      let attributes = new Map();
+      for (let i = 0; i < json.attrib_list.length; i += 2) {
+        attributes.set(json.attrib_list[i], json.attrib_list[i + 1]);
+      }
+      return new Graphics3D(instance, attributes);
+    },
+
+    /**
+     * int32_t ResizeBuffers(
+     *     [in] PP_Resource context,
+     *     [in] int32_t width,
+     *     [in] int32_t height);
+     */
+    PPB_Graphics3D_ResizeBuffers: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      graphics.changeSize(json.width, json.height);
+      return PP_OK;
+    },
+
+    /**
+     * int32_t SwapBuffers(
+     *     [in] PP_Resource context,
+     *     [in] PP_CompletionCallback callback);
+     */
+    PPB_Graphics3D_SwapBuffers: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      return graphics.flush(json.callback);
+    },
+
+
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance,
+     *     [in] PP_ImageDataFormat format,
+     *     [in] PP_Size size,
+     *     [in] PP_Bool init_to_zero);
+     */
+    PPB_ImageData_Create: function(json) {
+      return new ImageData(this.instances[json.instance],
+                           PP_ImageDataFormat[json.format],
+                           json.size);
+    },
+
+    /**
+     * PP_Bool Describe(
+     *     [in] PP_Resource image_data,
+     *     [out] PP_ImageDataDesc desc);
+     */
+    PPB_ImageData_Describe: function(json) {
+      let imageData = PP_Resource.lookup(json.image_data);
+      return [PP_Bool.PP_TRUE, { desc: { format: imageData.format, size: imageData.size, stride: imageData.stride } }];
+    },
+
+    /**
+     * mem_t Map(
+     *     [in] PP_Resource image_data);
+     */
+    PPB_ImageData_Map: function(json) {
+      let imageData = PP_Resource.lookup(json.image_data);
+      return imageData.map();
+    },
+
+
+    /**
+     * int32_t RequestInputEvents([in] PP_Instance instance,
+     *                            [in] uint32_t event_classes);
+     */
+    PPB_InputEvent_RequestInputEvents: function(json) {
+      let instance = this.instances[json.instance];
+      return instance.registerEventHandler(json.event_classes, false);
+    },
+
+    /**
+     * int32_t RequestFilteringInputEvents([in] PP_Instance instance,
+     *                                     [in] uint32_t event_classes);
+     */
+    PPB_InputEvent_RequestFilteringInputEvents: function(json) {
+      let instance = this.instances[json.instance];
+      return instance.registerEventHandler(json.event_classes, true);
+    },
+
+    /**
+     * PP_Bool IsInputEvent([in] PP_Resource resource);
+     */
+    PPB_InputEvent_IsInputEvent: function(json) {
+      let event = PP_Resource.lookup(json.resource);
+      return event instanceof InputEvent ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE;
+    },
+
+    /**
+     * PP_InputEvent_Type GetType([in] PP_Resource event);
+     */
+    PPB_InputEvent_GetType: function(json) {
+      let event = PP_Resource.lookup(json.event);
+      return event.eventType;
+    },
+
+    /**
+     * PP_TimeTicks GetTimeStamp([in] PP_Resource event);
+     */
+    PPB_InputEvent_GetTimeStamp: function(json) {
+      let event = PP_Resource.lookup(json.event);
+      return event.timeStamp;
+    },
+
+    /**
+     * uint32_t GetModifiers([in] PP_Resource event);
+     */
+    PPB_InputEvent_GetModifiers: function(json) {
+      let event = PP_Resource.lookup(json.event);
+      let modifiers = 0;
+      for (let [gecko, ppapi] of ModifierMap) {
+        if (event.domEvent.getModifierState(gecko)) {
+          modifiers |= ppapi;
+        }
+      }
+
+      if (event instanceof KeyboardInputEvent) {
+        if (event.domEvent.location == event.domEvent.DOM_KEY_LOCATION_NUMPAD) {
+          modifiers &= PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ISKEYPAD;
+        } else if (event.domEvent.location & event.domEvent.DOM_KEY_LOCATION_LEFT) {
+          modifiers &= PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ISLEFT;
+        } else if (event.domEvent.location & event.domEvent.DOM_KEY_LOCATION_RIGHT) {
+          modifiers &= PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ISRIGHT;
+        }
+
+        if (event.domEvent.repeat) {
+          modifiers &= PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ISAUTOREPEAT;
+        }
+      } else if (event instanceof MouseInputEvent) {
+        if (event.domEvent.buttons && 0x01) {
+          modifiers &= PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN;
+        }
+        if (event.domEvent.buttons && 0x04) {
+          modifiers &= PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN;
+        }
+        if (event.domEvent.buttons && 0x02) {
+          modifiers &= PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_RIGHTBUTTONDOWN;
+        }
+      }
+
+      dump("MODIFIERS " + modifiers + "\n");
+      return modifiers;
+    },
+
+
+    /**
+     * PP_Bool BindGraphics(
+     *     [in] PP_Instance instance,
+     *     [in] PP_Resource device);
+     */
+    PPB_Instance_BindGraphics: function(json) {
+      let instance = this.instances[json.instance];
+      if (json.device === 0) {
+        instance.bindGraphics(undefined);
+      } else {
+        instance.bindGraphics(PP_Resource.lookup(json.device));
+      }
+      return PP_Bool.PP_TRUE;
+    },
+
+    /**
+     * PP_Bool IsFullFrame(
+     *     [in] PP_Instance instance);
+     */
+    PPB_Instance_IsFullFrame: function(json) {
+      let instance = this.instances[json.instance];
+      return instance.info.isFullFrame ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE;
+    },
+
+
+    /**
+     * PP_Var GetWindowObject([in] PP_Instance instance);
+     */
+    PPB_Instance_Private_GetWindowObject: function(json) {
+      let instance = this.instances[json.instance];
+      return new Object_PP_Var(instance.containerWindow, instance);
+    },
+
+    /**
+     * PP_Var ExecuteScript([in] PP_Instance instance,
+     *                      [in] PP_Var script,
+     *                      [out] PP_Var exception);
+     */
+    PPB_Instance_Private_ExecuteScript: function(json) {
+      let instance = this.instances[json.instance];
+      let script = String_PP_Var.getAsJSValue(json.script);
+      let result = instance.mm.sendRpcMessage("ppapiflash.js:executeScript", script, { instance })[0];
+      return [result, { exception: null }];
+      //return [new PP_Var(), { exception: PP_Var.fromJSValue(e) }];
+    },
+
+
+    /**
+     * PP_Bool IsKeyboardInputEvent([in] PP_Resource resource);
+     */
+    PPB_KeyboardInputEvent_IsKeyboardInputEvent: function(json) {
+      let resource = PP_Resource.lookup(json.resource);
+      return resource instanceof KeyboardInputEvent ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE;
+    },
+
+    /**
+     * uint32_t GetKeyCode([in] PP_Resource key_event);
+     */
+    PPB_KeyboardInputEvent_GetKeyCode: function(json) {
+      let event = PP_Resource.lookup(json.key_event);
+      return event.domEvent.keyCode;
+    },
+
+    /**
+     * PP_Var GetCharacterText([in] PP_Resource character_event);
+     */
+    PPB_KeyboardInputEvent_GetCharacterText: function(json) {
+      let event = PP_Resource.lookup(json.character_event);
+      let charCode = event.domEvent.charCode;
+      if (charCode === 0) {
+        return new PP_Var();
+      }
+      return new String_PP_Var(String.fromCharCode(charCode));
+    },
+
+
+    /**
+     * void PostMessage([in] PP_Instance instance, [in] PP_Var message);
+     */
+    PPB_Messaging_PostMessage: function(json) {
+      if (PP_VarType[json.message.type] != PP_VarType.PP_VARTYPE_DICTIONARY) {
+        return;
+      }
+      let instance = this.instances[json.instance];
+      let message = PP_Var.toJSValue(json.message, instance);
+      instance.viewport.notify(message);
+    },
+
+    /**
+     * void SetTextInputType([in] PP_Instance instance,
+     *                       [in] PP_TextInput_Type_Dev type);
+     */
+    PPB_TextInput_Dev_SetTextInputType: function(json) {
+      let instance = this.instances[json.instance];
+      let inputBox = instance.window.document.getElementById("IMEInput");
+      switch (PP_TextInput_Type_Dev[json.type]) {
+        case PP_TextInput_Type_Dev.PP_TEXTINPUT_TYPE_DEV_TEXT:
+          if (inputBox) {
+            inputBox.value = "";
+            // we use setTimeout here to hold a small amount of time
+            // to make sure that the focus state is stable when we
+            // try to transfer focus. Same reason as the one later.
+            instance.window.setTimeout(function() { inputBox.focus(); });
+            return;
+          }
+          inputBox = instance.window.document.createElement("input");
+          inputBox.type = "text";
+          inputBox.id = "IMEInput";
+          inputBox.style.bottom = "10px";
+          inputBox.style.opacity = 0;
+          inputBox.style.position = "fixed";
+          inputBox.style.tabindex = -1;
+          inputBox.style.zIndex = -1;
+          instance.window.document.body.appendChild(inputBox);
+          inputBox.addEventListener("blur", () => {
+            instance.window.setTimeout(function() {
+              // in PP_TEXTINPUT_TYPE_DEV_TEXT state, we only release the focus of inputBox
+              // when the plugin(viewport) doesn't have focus.
+              if (instance.viewport.is(instance.window.document.activeElement)) {
+                inputBox.focus();
+              } else {
+                // if both inputBox and viewport don't have focus,
+                // we might need to inform the plugin that it has lost focus.
+                instance.didChangeFocus();
+              }
+            });
+          });
+          // when plugins accept text input but doesn't register text input related APIs,
+          // the characters are delievered to plugins via PP_INPUTEVENT_TYPE_CHAR.
+          if (!(instance.eventHandlers & PP_InputEvent_Class.PP_INPUTEVENT_CLASS_IME)) {
+            inputBox.addEventListener("compositionstart", () => {
+              inputBox.value = "";
+              inputBox.style.opacity = 1;
+              inputBox.style.zIndex = 1;
+            });
+            inputBox.addEventListener("compositionupdate", (e) => {
+              if (!e.data) {
+                inputBox.style.opacity = 0;
+                inputBox.style.zIndex = -1;
+              }
+            });
+            inputBox.addEventListener("compositionend", (e) => {
+              inputBox.style.opacity = 0;
+              inputBox.style.zIndex = -1;
+              for (let i = 0; i < e.data.length; i++) {
+                let keyboardEventInit = {
+                  charCode: e.data.charCodeAt(i)
+                };
+                let event = new instance.window.KeyboardEvent("keypress", keyboardEventInit);
+                instance.handleEvent(event);
+              }
+            });
+          }
+          instance.window.setTimeout(function() { inputBox.focus(); });
+          break;
+        default:
+          if (inputBox) {
+            instance.window.document.body.removeChild(inputBox);
+          }
+      }
+    },
+
+     /**
+      * void UpdateCaretPosition([in] PP_Instance instance,
+      *                          [in] PP_Rect caret,
+      *                          [in] PP_Rect bounding_box);
+      */
+     PPB_TextInput_Dev_UpdateCaretPosition: function(json) {
+       let instance = this.instances[json.instance];
+       let inputBox = instance.window.document.getElementById("IMEInput");
+       if (inputBox) {
+         inputBox.style.left = json.bounding_box.point.x + 'px';
+         inputBox.style.top = json.bounding_box.point.y + 'px';
+       }
+     },
+
+    /**
+     * PP_Bool IsIMEInputEvent([in] PP_Resource resource);
+     */
+    PPB_IMEInputEvent_Dev_IsIMEInputEvent: function(json) {
+      let resource = PP_Resource.lookup(json.resource);
+      return resource instanceof IMEInputEvent ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE;
+    },
+
+    /**
+     * PP_Var GetText([in] PP_Resource ime_event);
+     */
+    PPB_IMEInputEvent_Dev_GetText: function(json) {
+      let event = PP_Resource.lookup(json.ime_event);
+      return new String_PP_Var(event.domEvent.data);
+    },
+
+
+    /**
+     * uint32_t GetSegmentNumber([in] PP_Resource ime_event)
+     */
+    PPB_IMEInputEvent_Dev_GetSegmentNumber: function(json) {
+      let resource = PP_Resource.lookup(json.ime_event);
+      if (resource.eventType == PP_InputEvent_Type.PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE) {
+        let clauseArray = resource.domEvent.ranges;
+        if (clauseArray && clauseArray.length) {
+          return clauseArray.length - 1;
+        }
+      }
+      return 0;
+    },
+
+    /**
+     * int32_t GetTargetSegment([in] PP_Resource ime_event);
+     */
+    PPB_IMEInputEvent_Dev_GetTargetSegment: function(json) {
+      let resource = PP_Resource.lookup(json.ime_event);
+      if (resource.eventType == PP_InputEvent_Type.PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE) {
+        if (resource.targetSegment != undefined) {
+          return resource.targetSegment;
+        }
+      }
+      return -1;
+    },
+
+    /**
+     * uint32_t GetSegmentOffset([in] PP_Resource ime_event,[in] uint32_t index)
+     */
+    PPB_IMEInputEvent_Dev_GetSegmentOffset: function(json) {
+      let resource = PP_Resource.lookup(json.ime_event);
+      if (resource.eventType == PP_InputEvent_Type.PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE) {
+        let index = json.index;
+        if (resource.segmentOffset && resource.segmentOffset[index]) {
+          return resource.segmentOffset[index];
+        }
+      }
+      return 0;
+    },
+
+    /**
+     * void GetSelection([in] PP_Resource ime_event,
+     *                   [out] uint32_t start,
+     *                   [out] uint32_t end);
+     */
+     PPB_IMEInputEvent_Dev_GetSelection: function(json) {
+       let resource = PP_Resource.lookup(json.ime_event);
+       let startOffset, endOffset;
+       // Note: vaild targetSegment or segmentOffset could be zero.
+       if (resource.targetSegment != undefined &&
+           resource.segmentOffset[resource.targetSegment] != undefined &&
+           resource.segmentOffset[resource.targetSegment + 1] != undefined) {
+           startOffset = resource.segmentOffset[resource.targetSegment];
+           endOffset = resource.segmentOffset[resource.targetSegment + 1];
+       } else {
+         // Spec doesn't define what values to return if there's no selected text.
+         // In this case, startOffset and endOffset should be the same.
+         // Here we set them to the end offset of the text.
+         let encoder = new TextEncoder("utf-8");
+         let data = resource.domEvent.data;
+         startOffset = endOffset = encoder.encode(data).length;
+       }
+       return [{ start: startOffset, end: endOffset }];
+     },
+
+    /**
+     * PP_Resource Create([in] PP_Instance instance,
+     *                    [in] PP_InputEvent_Type type,
+     *                    [in] PP_TimeTicks time_stamp,
+     *                    [in] uint32_t modifiers,
+     *                    [in] PP_InputEvent_MouseButton mouse_button,
+     *                    [in] PP_Point mouse_position,
+     *                    [in] int32_t click_count,
+     *                    [in] PP_Point mouse_movement);
+     */
+    PPB_MouseInputEvent_Create: function(json) {
+      let instance = this.instances[json.instance];
+      let mouseEventInit = {
+        altkey: PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ALTKEY &
+          json.modifiers,
+        button: PP_InputEvent_MouseButton[json.mouse_button],
+        clientX: json.mouse_position.x,
+        clientY: json.mouse_position.y,
+        ctrlKey: PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_CONTROLKEY &
+          json.modifiers,
+        detail: json.click_count,
+        metaKey: PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_METAKEY &
+          json.modifiers,
+        movementX: json.mouse_movement.x,
+        movementY: json.mouse_movement.y,
+        shiftKey: PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_SHIFTKEY &
+          json.modifiers
+      };
+      let eventName = EventByTypes.get(PP_InputEvent_Type[json.type]);
+      let event = new instance.window.MouseEvent(eventName, mouseEventInit);
+      let resource = new MouseInputEvent(instance, event);
+      resource.timeStamp = json.time_stamp;
+      return resource;
+    },
+
+    /**
+     * PP_Bool IsMouseInputEvent([in] PP_Resource resource);
+     */
+    PPB_MouseInputEvent_IsMouseInputEvent: function(json) {
+      let resource = PP_Resource.lookup(json.resource);
+      return resource instanceof MouseInputEvent ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE;
+    },
+
+    /**
+     * PP_InputEvent_MouseButton GetButton([in] PP_Resource mouse_event);
+     */
+    PPB_MouseInputEvent_GetButton: function(json) {
+      let event = PP_Resource.lookup(json.mouse_event);
+      let button = event.domEvent.button;
+      switch (button) {
+        case -1:
+          return PP_InputEvent_MouseButton.PP_INPUTEVENT_MOUSEBUTTON_NONE;
+        case 0:
+          return PP_InputEvent_MouseButton.PP_INPUTEVENT_MOUSEBUTTON_LEFT;
+        case 1:
+          return PP_InputEvent_MouseButton.PP_INPUTEVENT_MOUSEBUTTON_MIDDLE;
+        case 2:
+          return PP_InputEvent_MouseButton.PP_INPUTEVENT_MOUSEBUTTON_RIGHT;
+        default:
+          return PP_InputEvent_MouseButton.PP_INPUTEVENT_MOUSEBUTTON_LEFT;
+      }
+    },
+
+    /**
+     * PP_Point GetPosition([in] PP_Resource mouse_event)
+     */
+    PPB_MouseInputEvent_GetPosition: function(json) {
+      let event = PP_Resource.lookup(json.mouse_event);
+      return { x: event.domEvent.clientX, y: event.domEvent.clientY };
+    },
+
+    /**
+     * int32_t GetClickCount([in] PP_Resource mouse_event);
+     */
+    PPB_MouseInputEvent_GetClickCount: function(json) {
+      let event = PP_Resource.lookup(json.mouse_event);
+      return event.domEvent.detail;
+    },
+
+    /**
+     * PP_Point GetMovement([in] PP_Resource mouse_event);
+     */
+    PPB_MouseInputEvent_GetMovement: function(json) {
+      let event = PP_Resource.lookup(json.mouse_event);
+      return { x: event.domEvent.movementX, y: event.domEvent.movementY };
+    },
+
+
+    /**
+     * PP_Resource Create([in] PP_Instance instance);
+     */
+    PPB_NetworkMonitor_Create: function(json) {
+      return new NetworkMonitor(this.instances[json.instance]);
+    },
+
+    /**
+     * int32_t UpdateNetworkList([in] PP_Resource network_monitor,
+     *                           [out] PP_Resource network_list,
+     *                           [in] PP_CompletionCallback callback);
+     */
+    PPB_NetworkMonitor_UpdateNetworkList: function(json) {
+      return [ PP_ERROR_NOACCESS, { network_list: null } ];
+    },
+
+
+    /**
+     * void ActiveTexture([in] PP_Resource context,
+     *                    [in] GLenum texture);
+     */
+    PPB_OpenGLES2_ActiveTexture: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.activeTexture(json.texture);
+    },
+
+    /**
+     * void AttachShader([in] PP_Resource context,
+     *                   [in] GLuint program,
+     *                   [in] GLuint shader);
+     */
+    PPB_OpenGLES2_AttachShader: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let program = graphics.objects.lookup(json.program);
+      let shader = graphics.objects.lookup(json.shader);
+      context.attachShader(program, shader);
+    },
+
+    /**
+     * void BindAttribLocation([in] PP_Resource context,
+     *                         [in] GLuint program,
+     *                         [in] GLuint index,
+     *                         [in] cstr_t name);
+     */
+    PPB_OpenGLES2_BindAttribLocation: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let program = graphics.objects.lookup(json.program);
+      context.bindAttribLocation(program, json.index, json.name);
+    },
+
+    /**
+     * void BindBuffer([in] PP_Resource context,
+     *                 [in] GLenum target,
+     *                 [in] GLuint buffer);
+     */
+    PPB_OpenGLES2_BindBuffer: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let buffer = graphics.objects.lookup(json.buffer);
+      context.bindBuffer(json.target, buffer);
+    },
+
+    /**
+     * void BindFramebuffer([in] PP_Resource context,
+     *                      [in] GLenum target,
+     *                      [in] GLuint framebuffer);
+     */
+    PPB_OpenGLES2_BindFramebuffer: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let frameBuffer = PP_Resource.lookup(json.framebuffer);
+      context.bindFramebuffer(json.target, frameBuffer);
+    },
+
+    /**
+     * void BindRenderbuffer([in] PP_Resource context,
+     *                       [in] GLenum target,
+     *                       [in] GLuint renderbuffer);
+     */
+    PPB_OpenGLES2_BindRenderbuffer: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let renderBuffer = PP_Resource.lookup(json.renderbuffer);
+      context.bindRenderbuffer(json.target, renderBuffer);
+    },
+
+    /**
+     * void BindTexture([in] PP_Resource context,
+     *                  [in] GLenum target,
+     *                  [in] GLuint texture);
+     */
+    PPB_OpenGLES2_BindTexture: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let texture = graphics.objects.lookup(json.texture);
+      context.bindTexture(json.target, texture);
+    },
+
+    /**
+     * void BlendColor([in] PP_Resource context,
+     *                 [in] GLclampf red,
+     *                 [in] GLclampf green,
+     *                 [in] GLclampf blue,
+     *                 [in] GLclampf alpha);
+     */
+    PPB_OpenGLES2_BlendColor: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.blendColor(json.red, json.green, json.blue, json.alpha);
+    },
+
+    /**
+     * void BlendEquation([in] PP_Resource context,
+     *                    [in] GLenum mode);
+     */
+    PPB_OpenGLES2_BlendEquation: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.blendEquation(json.mode);
+    },
+
+    /**
+     * void BlendEquationSeparate([in] PP_Resource context,
+     *                            [in] GLenum modeRGB,
+     *                            [in] GLenum modeAlpha);
+     */
+    PPB_OpenGLES2_BlendEquationSeparate: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.blendEquationSeparate(json.modeRGB, json.modeAlpha);
+    },
+
+    /**
+     * void BlendFunc([in] PP_Resource context,
+     *                [in] GLenum sfactor,
+     *                [in] GLenum dfactor);
+     */
+    PPB_OpenGLES2_BlendFunc: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.blendFunc(json.sfactor, json.dfactor);
+    },
+
+    /**
+     * void BlendFuncSeparate([in] PP_Resource context,
+     *                        [in] GLenum srcRGB,
+     *                        [in] GLenum dstRGB,
+     *                        [in] GLenum srcAlpha,
+     *                        [in] GLenum dstAlpha);
+     */
+    PPB_OpenGLES2_BlendFuncSeparate: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.blendFuncSeparate(json.srcRGB, json.dstRGB, json.srcAlpha, json.dstAlpha);
+    },
+
+    /**
+     * void BufferData([in] PP_Resource context,
+     *                 [in] GLenum target,
+     *                 [in] GLsizeiptr size,
+     *                 [in] mem_t data,
+     *                 [in] GLenum usage);
+     */
+    PPB_OpenGLES2_BufferData: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let data = this.copyBuffer(json.data, json.size);
+      context.bufferData(json.target, data, json.usage);
+    },
+
+    /**
+     * void BufferSubData([in] PP_Resource context,
+     *                    [in] GLenum target,
+     *                    [in] GLintptr offset,
+     *                    [in] GLsizeiptr size,
+     *                    [in] mem_t data);
+     */
+    PPB_OpenGLES2_BufferSubData: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let data = this.copyBuffer(json.data, json.size);
+      context.bufferSubData(json.target, json.offset, data);
+    },
+
+    /**
+     * GLenum CheckFramebufferStatus([in] PP_Resource context,
+     *                               [in] GLenum target);
+     */
+    PPB_OpenGLES2_CheckFramebufferStatus: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      return context.checkFramebufferStatus(json.target);
+    },
+
+    /**
+     * void Clear([in] PP_Resource context,
+     *            [in] GLbitfield mask);
+     */
+    PPB_OpenGLES2_Clear: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.clear(json.mask);
+    },
+
+    /**
+     * void ClearColor([in] PP_Resource context,
+     *                 [in] GLclampf red,
+     *                 [in] GLclampf green,
+     *                 [in] GLclampf blue,
+     *                 [in] GLclampf alpha);
+     */
+    PPB_OpenGLES2_ClearColor: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.clearColor(json.red, json.green, json.blue, json.alpha);
+    },
+
+    /**
+     * void ClearDepthf([in] PP_Resource context,
+     *                  [in] GLclampf depth);
+     */
+    PPB_OpenGLES2_ClearDepthf: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.clearDepthf(json.depth);
+    },
+
+    /**
+     * void ColorMask([in] PP_Resource context,
+     *                [in] GLboolean red,
+     *                [in] GLboolean green,
+     *                [in] GLboolean blue,
+     *                [in] GLboolean alpha);
+     */
+    PPB_OpenGLES2_ColorMask: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.colorMask(json.red, json.green, json.blue, json.alpha);
+    },
+
+    /**
+     * void CompileShader([in] PP_Resource context,
+     *                    [in] GLuint shader);
+     */
+    PPB_OpenGLES2_CompileShader: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let shader = graphics.objects.lookup(json.shader);
+      context.compileShader(shader);
+    },
+
+    /**
+     * void CopyTexImage2D([in] PP_Resource context,
+     *                     [in] GLenum target,
+     *                     [in] GLint level,
+     *                     [in] GLenum internalformat,
+     *                     [in] GLint x,
+     *                     [in] GLint y,
+     *                     [in] GLsizei width,
+     *                     [in] GLsizei height,
+     *                     [in] GLint border);
+     */
+    PPB_OpenGLES2_CopyTexImage2D: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.copyTexImage2D(json.target, json.level, json.internalformat,
+                             json.x, json.y, json.width, json.height,
+                             json.border);
+    },
+
+    /**
+     * void CopyTexSubImage2D([in] PP_Resource context,
+     *                        [in] GLenum target,
+     *                        [in] GLint level,
+     *                        [in] GLint xoffset,
+     *                        [in] GLint yoffset,
+     *                        [in] GLint x,
+     *                        [in] GLint y,
+     *                        [in] GLsizei width,
+     *                        [in] GLsizei height);
+     */
+    PPB_OpenGLES2_CopyTexSubImage2D: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.copyTexSubImage2D(json.target, json.level, json.xoffset,
+                                json.yoffset, json.x, json.y, json.width,
+                                json.height);
+    },
+
+    /**
+     * GLuint CreateProgram([in] PP_Resource context);
+     */
+    PPB_OpenGLES2_CreateProgram: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      return graphics.objects.add(graphics.context.createProgram());
+    },
+
+    /**
+     * GLuint CreateShader([in] PP_Resource context,
+     *                     [in] GLenum type);
+     */
+    PPB_OpenGLES2_CreateShader: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      return graphics.objects.add(graphics.context.createShader(json.type));
+    },
+
+    /**
+     * void CullFace([in] PP_Resource context,
+     *               [in] GLenum mode);
+     */
+    PPB_OpenGLES2_CullFace: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.cullFace(json.mode);
+    },
+
+    /**
+     * void DeleteBuffers([in] PP_Resource context,
+     *                    [in] GLsizei n,
+     *                    [in, size_is(n)] GLuint[] buffers);
+     */
+    PPB_OpenGLES2_DeleteBuffers: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      for (let i = 0; i < json.n; ++i) {
+        context.deleteBuffer(graphics.objects.lookup(json.buffers[i]));
+        graphics.objects.destroy(json.buffers[i]);
+      }
+    },
+
+    /**
+     * void DeleteFramebuffers([in] PP_Resource context,
+     *                         [in] GLsizei n,
+     *                         [in, size_is(n)] GLuint[] buffers);
+     */
+    PPB_OpenGLES2_DeleteFramebuffers: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      for (let i = 0; i < json.n; ++i) {
+        context.deleteFramebuffer(graphics.objects.lookup(json.buffers[i]));
+        graphics.objects.destroy(json.buffers[i]);
+      }
+    },
+
+    /**
+     * void DeleteProgram([in] PP_Resource context,
+     *                    [in] GLuint program);
+     */
+    PPB_OpenGLES2_DeleteProgram: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.deleteProgram(graphics.objects.lookup(json.program));
+      graphics.objects.destroy(json.program);
+    },
+
+    /**
+     * void DeleteRenderbuffers([in] PP_Resource context,
+     *                          [in] GLsizei n,
+     *                          [in, size_is(n)] GLuint[] renderbuffers);
+     */
+    PPB_OpenGLES2_DeleteRenderbuffers: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      for (let i = 0; i < json.n; ++i) {
+        context.deleteRenderbuffer(graphics.objects.lookup(json.renderbuffers[i]));
+        graphics.objects.destroy(json.renderbuffers[i]);
+      }
+    },
+
+    /**
+     * void DeleteShader([in] PP_Resource context,
+     *                   [in] GLuint shader);
+     */
+    PPB_OpenGLES2_DeleteShader: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.deleteShader(graphics.objects.lookup(json.shader));
+      graphics.objects.destroy(json.shader);
+    },
+
+    /**
+     * void DeleteTextures([in] PP_Resource context,
+     *                     [in] GLsizei n,
+     *                     [in, size_is(n)] GLuint[] textures);
+     */
+    PPB_OpenGLES2_DeleteTextures: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      for (let i = 0; i < json.n; ++i) {
+        context.deleteTexture(graphics.objects.lookup(json.textures[i]));
+        graphics.objects.destroy(json.textures[i]);
+      }
+    },
+
+    /**
+     * void DepthFunc([in] PP_Resource context,
+     *                [in] GLenum func);
+     */
+    PPB_OpenGLES2_DepthFunc: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.depthFunc(json.func);
+    },
+
+    /**
+     * void DepthMask([in] PP_Resource context,
+     *                [in] GLboolean flag);
+     */
+    PPB_OpenGLES2_DepthMask: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.depthMask(json.flag);
+    },
+
+    /**
+     * void DepthRangef([in] PP_Resource context,
+     *                  [in] GLclampf zNear,
+     *                  [in] GLclampf zFar);
+     */
+    PPB_OpenGLES2_DepthRangef: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.depthRangef(json.zNear, json.zFar);
+    },
+
+    /**
+     * void DetachShader([in] PP_Resource context,
+     *                   [in] GLuint program,
+     *                   [in] GLuint shader);
+     */
+    PPB_OpenGLES2_DetachShader: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.detachShader(json.program, graphics.objects.lookup(json.shader));
+    },
+
+    /**
+     * void Disable([in] PP_Resource context,
+     *              [in] GLenum cap);
+     */
+    PPB_OpenGLES2_Disable: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.disable(json.cap);
+    },
+
+    /**
+     * void DisableVertexAttribArray([in] PP_Resource context,
+     *                               [in] GLuint index);
+     */
+    PPB_OpenGLES2_DisableVertexAttribArray: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.disableVertexAttribArray(json.index);
+    },
+
+    /**
+     * void DrawArrays([in] PP_Resource context,
+     *                 [in] GLenum mode,
+     *                 [in] GLint first,
+     *                 [in] GLsizei count);
+     */
+    PPB_OpenGLES2_DrawArrays: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.drawArrays(json.mode, json.first, json.count);
+    },
+
+    /**
+     * void DrawElements([in] PP_Resource context,
+     *                   [in] GLenum mode,
+     *                   [in] GLsizei count,
+     *                   [in] GLenum type,
+     *                   [in] mem_t indices);
+     */
+    PPB_OpenGLES2_DrawElements: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.drawElements(json.mode, json.count, json.type, json.indices);
+    },
+
+    /**
+     * void Enable([in] PP_Resource context,
+     *             [in] GLenum cap);
+     */
+    PPB_OpenGLES2_Enable: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.enable(json.cap);
+    },
+
+    /**
+     * void EnableVertexAttribArray([in] PP_Resource context,
+     *                              [in] GLuint index);
+     */
+    PPB_OpenGLES2_EnableVertexAttribArray: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.enableVertexAttribArray(json.index);
+    },
+
+    /**
+     * void Finish([in] PP_Resource context);
+     */
+    PPB_OpenGLES2_Finish: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.finish();
+    },
+
+    /**
+     * void Flush([in] PP_Resource context);
+     */
+    PPB_OpenGLES2_Flush: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.flush();
+    },
+
+    /**
+     * void FramebufferTexture2D([in] PP_Resource context,
+     *                           [in] GLenum target,
+     *                           [in] GLenum attachment,
+     *                           [in] GLenum textarget,
+     *                           [in] GLuint texture,
+     *                           [in] GLint level);
+     */
+    PPB_OpenGLES2_FramebufferTexture2D: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.framebufferTexture2D(json.target, json.attachment, json.textarget, json.texture, json.level);
+    },
+
+    /**
+     * void FrontFace([in] PP_Resource context,
+     *                [in] GLenum mode);
+     */
+    PPB_OpenGLES2_FrontFace: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.frontFace(json.mode);
+    },
+
+    /**
+     * void GenBuffers([in] PP_Resource context,
+     *                 [in] GLsizei n,
+     *                 [inout, size_is(n)] GLuint[] buffers);
+     */
+    PPB_OpenGLES2_GenBuffers: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let buffers = [];
+      for (let i = 0; i < json.n; ++i) {
+        buffers.push(graphics.objects.add(context.createBuffer()));
+      }
+      return [{ buffers: buffers }];
+    },
+
+    /**
+     * void GenerateMipmap([in] PP_Resource context,
+     *                     [in] GLenum target);
+     */
+    PPB_OpenGLES2_GenerateMipmap: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.generateMipmap(json.target);
+    },
+
+    /**
+     * void GenFramebuffers([in] PP_Resource context,
+     *                      [in] GLsizei n,
+     *                      [inout, size_is(n)] GLuint[] framebuffers);
+     */
+    PPB_OpenGLES2_GenFramebuffers: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let framebuffers = [];
+      for (let i = 0; i < json.n; ++i) {
+        framebuffers.push(graphics.objects.add(context.createFramebuffer()));
+      }
+      return [{ framebuffers: framebuffers }];
+    },
+
+    /**
+     * void GenRenderbuffers([in] PP_Resource context,
+     *                       [in] GLsizei n,
+     *                       [inout, size_is(n)] GLuint[] renderbuffers);
+     */
+    PPB_OpenGLES2_GenRenderbuffers: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let renderbuffers = [];
+      for (let i = 0; i < json.n; ++i) {
+        renderbuffers.push(graphics.objects.add(context.createRenderbuffer()));
+      }
+      return [{ renderbuffers: renderbuffers }];
+    },
+
+    /**
+     * void GenTextures([in] PP_Resource context,
+     *                  [in] GLsizei n,
+     *                  [inout, size_is(n)] GLuint[] textures);
+     */
+    PPB_OpenGLES2_GenTextures: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let textures = [];
+      for (let i = 0; i < json.n; ++i) {
+        textures.push(graphics.objects.add(context.createTexture()));
+      }
+      return [{ textures: textures }];
+    },
+
+    /**
+     * GLint GetAttribLocation([in] PP_Resource context,
+     *                         [in] GLuint program,
+     *                         [in] cstr_t name);
+     */
+    PPB_OpenGLES2_GetAttribLocation: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      return context.getAttribLocation(json.program, json.name);
+    },
+
+    /**
+     * GLenum GetError([in] PP_Resource context);
+     */
+    PPB_OpenGLES2_GetError: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      return context.getError();
+    },
+
+    /**
+     * void GetFloatv([in] PP_Resource context,
+     *                [in] GLenum pname,
+     *                [out] GLint_ptr_t params);
+     */
+    PPB_OpenGLES2_GetFloatv: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      return [{ params: context.getParameter(json.pname) }];
+    },
+
+    /**
+     * void GetIntegerv([in] PP_Resource context,
+     *                  [in] GLenum pname,
+     *                  [out] GLint_ptr_t params);
+     */
+    PPB_OpenGLES2_GetIntegerv: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      return [{ params: context.getParameter(json.pname) }];
+    },
+
+    /**
+     * void GetProgramiv([in] PP_Resource context,
+     *                   [in] GLuint program,
+     *                   [in] GLenum pname,
+     *                   [out] GLint_ptr_t params);
+     */
+    PPB_OpenGLES2_GetProgramiv: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let program = graphics.objects.lookup(json.program);
+      let param = context.getProgramParameter(program, json.pname);
+      if (param === null) {
+        param = context.getError();
+      } else if (typeof param == "boolean") {
+        param = param ? 1 : 0;
+      }
+      return [{ params: param }];
+    },
+
+    /**
+     * void GetShaderiv([in] PP_Resource context,
+     *                  [in] GLuint shader,
+     *                  [in] GLenum pname,
+     *                  [out] GLint_ptr_t params);
+     */
+    PPB_OpenGLES2_GetShaderiv: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let shader = graphics.objects.lookup(json.shader);
+      let param;
+      if (json.pname == 0x8b84) { // GL_INFO_LOG_LENGTH
+        let infoLog = context.getShaderInfoLog(shader);
+        if (infoLog) {
+          param = infoLog.length;
+          if (param > 0) {
+            param += 1;
+          }
+        } else {
+          param = context.getError();
+        }
+      } else {
+        param = context.getShaderParameter(shader, json.pname);
+        if (param === null) {
+          param = context.getError();
+        } else if (typeof param == "boolean") {
+          param = param ? 1 : 0;
+        }
+      }
+      return [{ params: param }];
+    },
+
+    /**
+     * void GetShaderInfoLog([in] PP_Resource context,
+     *                       [in] GLuint shader,
+     *                       [in] GLsizei bufsize,
+     *                       [out] GLsizei_ptr_t length,
+     *                       [out] str_t infolog);
+     */
+    PPB_OpenGLES2_GetShaderInfoLog: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let shader = graphics.objects.lookup(json.shader);
+      let log = context.getShaderInfoLog(shader) + " ";
+      return [{ length: log.length, infolog: log }];
+    },
+
+    /**
+     * GLubyte_ptr_t GetString([in] PP_Resource context,
+     *                         [in] GLenum name);
+     */
+    PPB_OpenGLES2_GetString: function(json) {
+      let context = PP_Resource.lookup(json.context).context;
+      if (json.name == 0x1f03) { // GL_EXTENSIONS
+        let extensions = context.getSupportedExtensions();
+        extensions = extensions.map((v) => {
+          if (v.startsWith("EXT_") || v.startsWith("OES_")) {
+            return "GL_" + v;
+          }
+          if (v == "WEBGL_compressed_texture_s3tc") {
+            return "GL_EXT_texture_compression_s3tc";
+          }
+          if (v == "WEBGL_draw_buffers") {
+            return "GL_EXT_draw_buffers";
+          }
+          return v;
+        });
+        return extensions.join(" ");
+      }
+      return PP_Resource.lookup(json.context).context.getParameter(json.name);
+    },
+
+    /**
+     * GLint GetUniformLocation([in] PP_Resource context,
+     *                          [in] GLuint program,
+     *                          [in] cstr_t name);
+     */
+    PPB_OpenGLES2_GetUniformLocation: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let program = graphics.objects.lookup(json.program);
+      let location = context.getUniformLocation(program, json.name);
+      return graphics.objects.add(location);
+    },
+
+    /**
+     * void Hint([in] PP_Resource context,
+     *           [in] GLenum target,
+     *           [in] GLenum mode);
+     */
+    PPB_OpenGLES2_Hint: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.hint(json.target, json.mode);
+    },
+
+    /**
+     * GLboolean IsEnabled([in] PP_Resource context,
+     *                     [in] GLenum cap);
+     */
+    PPB_OpenGLES2_IsEnabled: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      return context.isEnabled(json.cap);
+    },
+
+    /**
+     * void LineWidth([in] PP_Resource context,
+     *                [in] GLfloat width);
+     */
+    PPB_OpenGLES2_LineWidth: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.lineWidth(json.width);
+    },
+
+    /**
+     * void LinkProgram([in] PP_Resource context,
+     *                  [in] GLuint program);
+     */
+    PPB_OpenGLES2_LinkProgram: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let program = graphics.objects.lookup(json.program);
+      context.linkProgram(program);
+    },
+
+    /**
+     * void PixelStorei([in] PP_Resource context,
+     *                  [in] GLenum pname,
+     *                  [in] GLint param);
+     */
+    PPB_OpenGLES2_PixelStorei: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.pixelStorei(json.pname, json.param);
+    },
+
+    /**
+     * void PolygonOffset([in] PP_Resource context,
+     *                    [in] GLfloat factor,
+     *                    [in] GLfloat units);
+     */
+    PPB_OpenGLES2_PolygonOffset: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.polygonOffset(json.factor, json.units);
+    },
+
+    /**
+     * void ReadPixels([in] PP_Resource context,
+     *                 [in] GLint x,
+     *                 [in] GLint y,
+     *                 [in] GLsizei width,
+     *                 [in] GLsizei height,
+     *                 [in] GLenum format,
+     *                 [in] GLenum type,
+     *                 [out] mem_t pixels);
+     */
+    PPB_OpenGLES2_ReadPixels: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let size = GLES2Utils.computeImageDataSize(context, json.width, json.height, json.format, json.type);
+      let bytes = GLES2Utils.bytesPerElement(context, json.type);
+      let source;
+      switch (bytes) {
+        case 4:
+          source = new Float32Array(size / 4);
+          break;
+        case 2:
+          source = new Uint16Array(size / 2);
+          break;
+        case 1:
+          source = new Uint8Array(size);
+          break;
+      }
+      context.readPixels(json.x, json.y, json.width, json.height, json.format, json.type, source);
+      this.setBuffer(json.pixels, source);
+      return [{ pixels: json.pixels }];
+    },
+
+    /**
+     * void ReleaseShaderCompiler([in] PP_Resource context);
+     */
+    PPB_OpenGLES2_ReleaseShaderCompiler: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.releaseShaderCompiler();
+    },
+
+    /**
+     * void RenderbufferStorage([in] PP_Resource context,
+     *                          [in] GLenum target,
+     *                          [in] GLenum internalformat,
+     *                          [in] GLsizei width,
+     *                          [in] GLsizei height);
+     */
+    PPB_OpenGLES2_RenderbufferStorage: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.renderbufferStorage(json.target, json.internalformat,
+                                  json.width, json.height);
+    },
+
+    /**
+     * void SampleCoverage([in] PP_Resource context,
+     *                     [in] GLclampf value,
+     *                     [in] GLboolean invert);
+     */
+    PPB_OpenGLES2_SampleCoverage: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.sampleCoverage(json.value, json.invert);
+    },
+
+    /**
+     * void Scissor([in] PP_Resource context,
+     *              [in] GLint x,
+     *              [in] GLint y,
+     *              [in] GLsizei width,
+     *              [in] GLsizei height);
+     */
+    PPB_OpenGLES2_Scissor: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.scissor(json.x, json.y, json.width, json.height);
+    },
+
+    /**
+     * void ShaderSource([in] PP_Resource context,
+     *                   [in] GLuint shader,
+     *                   [in] GLsizei count,
+     *                   [in, size_is(count)] str_t[] str,
+     *                   [in] GLint_ptr_t length);
+     */
+    PPB_OpenGLES2_ShaderSource: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let shader = graphics.objects.lookup(json.shader);
+      for (let i = 0; i < json.count; ++i) {
+        context.shaderSource(shader, json.str[i]);
+      }
+    },
+
+    /**
+     * void StencilFunc([in] PP_Resource context,
+     *                  [in] GLenum func,
+     *                  [in] GLint ref,
+     *                  [in] GLuint mask);
+     */
+    PPB_OpenGLES2_StencilFunc: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.stencilFunc(json.func, json.ref, json.mask);
+    },
+
+    /**
+     * void StencilFuncSeparate([in] PP_Resource context,
+     *                          [in] GLenum face,
+     *                          [in] GLenum func,
+     *                          [in] GLint ref,
+     *                          [in] GLuint mask);
+     */
+    PPB_OpenGLES2_StencilFuncSeparate: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.stencilFuncSeparate(json.face, json.func, json.ref, json.mask);
+    },
+
+    /**
+     * void StencilMask([in] PP_Resource context,
+     *                  [in] GLuint mask);
+     */
+    PPB_OpenGLES2_StencilMask: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.stencilMask(json.mask);
+    },
+
+    /**
+     * void StencilMaskSeparate([in] PP_Resource context,
+     *                          [in] GLenum face,
+     *                          [in] GLuint mask);
+     */
+    PPB_OpenGLES2_StencilMaskSeparate: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.stencilMaskSeparate(json.face, json.mask);
+    },
+
+    /**
+     * void StencilOp([in] PP_Resource context,
+     *                [in] GLenum fail,
+     *                [in] GLenum zfail,
+     *                [in] GLenum zpass);
+     */
+    PPB_OpenGLES2_StencilOp: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.stencilOp(json.fail, json.zfail, json.zpass);
+    },
+
+    /**
+     * void StencilOpSeparate([in] PP_Resource context,
+     *                        [in] GLenum face,
+     *                        [in] GLenum fail,
+     *                        [in] GLenum zfail,
+     *                        [in] GLenum zpass);
+     */
+    PPB_OpenGLES2_StencilOpSeparate: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.stencilOpSeparate(json.face, json.fail, json.zfail, json.zpass);
+    },
+
+    /**
+     * void TexImage2D([in] PP_Resource context,
+     *                 [in] GLenum target,
+     *                 [in] GLint level,
+     *                 [in] GLint internalformat,
+     *                 [in] GLsizei width,
+     *                 [in] GLsizei height,
+     *                 [in] GLint border,
+     *                 [in] GLenum format,
+     *                 [in] GLenum type,
+     *                 [in] mem_t pixels);
+     */
+    PPB_OpenGLES2_TexImage2D: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.texImage2D(json.target, json.level, json.internalformat,
+                         json.width, json.height, json.border, json.format,
+                         json.type, json.pixels);
+    },
+
+    /**
+     * void TexParameterf([in] PP_Resource context,
+     *                    [in] GLenum target,
+     *                    [in] GLenum pname,
+     *                    [in] GLfloat param);
+     */
+    PPB_OpenGLES2_TexParameterf: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.texParameterf(json.target, json.pname, json.param);
+    },
+
+    /**
+     * void TexParameteri([in] PP_Resource context,
+     *                    [in] GLenum target,
+     *                    [in] GLenum pname,
+     *                    [in] GLint param);
+     */
+    PPB_OpenGLES2_TexParameteri: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.texParameteri(json.target, json.pname, json.param);
+    },
+
+    /**
+     *  void TexSubImage2D([in] PP_Resource context,
+     *                     [in] GLenum target,
+     *                     [in] GLint level,
+     *                     [in] GLint xoffset,
+     *                     [in] GLint yoffset,
+     *                     [in] GLsizei width,
+     *                     [in] GLsizei height,
+     *                     [in] GLenum format,
+     *                     [in] GLenum type,
+     *                     [in] mem_t pixels);
+     */
+    PPB_OpenGLES2_TexSubImage2D: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let size = GLES2Utils.computeImageDataSize(context, json.width, json.height,
+                                                 json.format, json.type);
+      let data = this.copyBuffer(json.pixels, size);
+      let bytes = GLES2Utils.bytesPerElement(context, json.type);
+      let view;
+      switch (bytes) {
+        case 4:
+          view = new Float32Array(data);
+          break;
+        case 2:
+          view = new Uint16Array(data);
+          break;
+        case 1:
+          view = new Uint8Array(data);
+          break;
+      }
+      context.texSubImage2D(json.target, json.level, json.xoffset, json.yoffset,
+                            json.width, json.height, json.format, json.type, view);
+    },
+
+    /**
+     * void Uniform1f([in] PP_Resource context,
+     *                [in] GLint location,
+     *                [in] GLfloat x);
+     */
+    PPB_OpenGLES2_Uniform1f: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let location = graphics.objects.lookup(json.location);
+      context.uniform1f(location, json.x);
+    },
+
+    /**
+     * void Uniform1i([in] PP_Resource context,
+     *                [in] GLint location,
+     *                [in] GLint x);
+     */
+    PPB_OpenGLES2_Uniform1i: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let location = graphics.objects.lookup(json.location);
+      context.uniform1i(location, json.x);
+    },
+
+    /**
+     * void Uniform2f([in] PP_Resource context,
+     *                [in] GLint location,
+     *                [in] GLfloat x,
+     *                [in] GLfloat y);
+     */
+    PPB_OpenGLES2_Uniform2f: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let location = graphics.objects.lookup(json.location);
+      context.uniform2f(location, json.x, json.y);
+    },
+
+    /**
+     * void Uniform2i([in] PP_Resource context,
+     *                [in] GLint location,
+     *                [in] GLint x,
+     *                [in] GLint y);
+     */
+    PPB_OpenGLES2_Uniform2i: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let location = graphics.objects.lookup(json.location);
+      context.uniform2i(location, json.x, json.y);
+    },
+
+    /**
+     * void Uniform3f([in] PP_Resource context,
+     *                [in] GLint location,
+     *                [in] GLfloat x,
+     *                [in] GLfloat y,
+     *                [in] GLfloat z);
+     */
+    PPB_OpenGLES2_Uniform3f: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let location = graphics.objects.lookup(json.location);
+      context.uniform3f(location, json.x, json.y, json.z);
+    },
+
+    /**
+     * void Uniform3i([in] PP_Resource context,
+     *                [in] GLint location,
+     *                [in] GLint x,
+     *                [in] GLint y,
+     *                [in] GLint z);
+     */
+    PPB_OpenGLES2_Uniform3i: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let location = graphics.objects.lookup(json.location);
+      context.uniform3i(location, json.x, json.y, json.z);
+    },
+
+    /**
+     * void Uniform4fv([in] PP_Resource context,
+     *                 [in] GLint location,
+     *                 [in] GLsizei count,
+     *                 [in] GLfloat_ptr_t v);
+     */
+    PPB_OpenGLES2_Uniform4fv: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let location = graphics.objects.lookup(json.location);
+      let data = this.copyBuffer(json.v, json.count * 4);
+      context.uniform4fv(location, new Float32Array(data));
+    },
+
+    /**
+     * void Uniform4f([in] PP_Resource context,
+     *                [in] GLint location,
+     *                [in] GLfloat x,
+     *                [in] GLfloat y,
+     *                [in] GLfloat z,
+     *                [in] GLfloat w);
+     */
+    PPB_OpenGLES2_Uniform4f: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let location = graphics.objects.lookup(json.location);
+      context.uniform4f(location, json.x, json.y, json.z, json.w);
+    },
+
+    /**
+     * void Uniform4i([in] PP_Resource context,
+     *                [in] GLint location,
+     *                [in] GLint x,
+     *                [in] GLint y,
+     *                [in] GLint z,
+     *                [in] GLint w);
+     */
+    PPB_OpenGLES2_Uniform4i: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let location = graphics.objects.lookup(json.location);
+      context.uniform4i(location, json.x, json.y, json.z, json.w);
+    },
+
+    /**
+     * void UniformMatrix3fv([in] PP_Resource context,
+     *                       [in] GLint location,
+     *                       [in] GLsizei count,
+     *                       [in] GLboolean transpose,
+     *                       [in] GLfloat_ptr_t value);
+     */
+    PPB_OpenGLES2_UniformMatrix3fv: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let location = graphics.objects.lookup(json.location);
+      context.uniformMatrix3fv(location, json.transpose, json.value);
+    },
+
+    /**
+     * void UseProgram([in] PP_Resource context,
+     *                 [in] GLuint program);
+     */
+    PPB_OpenGLES2_UseProgram: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let program = graphics.objects.lookup(json.program);
+      context.useProgram(program);
+    },
+
+    /**
+     * void ValidateProgram([in] PP_Resource context,
+     *                      [in] GLuint program);
+     */
+    PPB_OpenGLES2_ValidateProgram: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let program = graphics.objects.lookup(json.program);
+      context.validateProgram(program);
+    },
+
+    /**
+     * void VertexAttrib1f([in] PP_Resource context,
+     *                     [in] GLuint indx,
+     *                     [in] GLfloat x);
+     */
+    PPB_OpenGLES2_VertexAttrib1f: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.vertexAttrib1f(json.indx, json.x);
+    },
+
+    /**
+     * void VertexAttrib2f([in] PP_Resource context,
+     *                     [in] GLuint indx,
+     *                     [in] GLfloat x,
+     *                     [in] GLfloat y);
+     */
+    PPB_OpenGLES2_VertexAttrib2f: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.vertexAttrib2f(json.indx, json.x, json.y);
+    },
+
+    /**
+     * void VertexAttrib3f([in] PP_Resource context,
+     *                     [in] GLuint indx,
+     *                     [in] GLfloat x,
+     *                     [in] GLfloat y,
+     *                     [in] GLfloat z);
+     */
+    PPB_OpenGLES2_VertexAttrib3f: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.vertexAttrib3f(json.indx, json.x, json.y, json.z);
+    },
+
+    /**
+     * void VertexAttrib4f([in] PP_Resource context,
+     *                     [in] GLuint indx,
+     *                     [in] GLfloat x,
+     *                     [in] GLfloat y,
+     *                     [in] GLfloat z,
+     *                     [in] GLfloat w);
+     */
+    PPB_OpenGLES2_VertexAttrib4f: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.vertexAttrib4f(json.indx, json.x, json.y, json.z, json.w);
+    },
+
+    /**
+     * void VertexAttribPointer([in] PP_Resource context,
+     *                          [in] GLuint indx,
+     *                          [in] GLint size,
+     *                          [in] GLenum type,
+     *                          [in] GLboolean normalized,
+     *                          [in] GLsizei stride,
+     *                          [in] mem_t ptr);
+     */
+    PPB_OpenGLES2_VertexAttribPointer: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      if (json.ptr !== null)
+        throw Error("Need to convert ptr to an offset!");
+      context.vertexAttribPointer(json.indx, json.size, json.type,
+                                  json.normalized, json.stride, json.ptr);
+    },
+
+    /**
+     * void Viewport([in] PP_Resource context,
+     *               [in] GLint x,
+     *               [in] GLint y,
+     *               [in] GLsizei width,
+     *               [in] GLsizei height);
+     */
+    PPB_OpenGLES2_Viewport: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      context.viewport(json.x, json.y, json.width, json.height);
+    },
+
+
+    /**
+     * mem_t MapTexSubImage2DCHROMIUM([in] PP_Resource context,
+     *                                [in] GLenum target,
+     *                                [in] GLint level,
+     *                                [in] GLint xoffset,
+     *                                [in] GLint yoffset,
+     *                                [in] GLsizei width,
+     *                                [in] GLsizei height,
+     *                                [in] GLenum format,
+     *                                [in] GLenum type,
+     *                                [in] GLenum access);
+     */
+    PPB_OpenGLES2ChromiumMapSub_MapTexSubImage2DCHROMIUM: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let size = GLES2Utils.computeImageDataSize(context, json.width, json.height,
+                                                 json.format, json.type);
+      let mem = this.allocateCachedBuffer(size);
+      graphics.mappedTextures.set(mem, () => {
+        let source = this.getCachedBuffer(mem);
+        let bytes = GLES2Utils.bytesPerElement(context, json.type);
+        let view;
+        switch (bytes) {
+          case 4:
+            view = new Float32Array(source);
+            break;
+          case 2:
+            view = new Uint16Array(source);
+            break;
+          case 1:
+            view = new Uint8Array(source);
+            break;
+        }
+        context.texSubImage2D(json.target, json.level, json.xoffset,
+                              json.yoffset, json.width, json.height,
+                              json.format, json.type, view);
+        this.freeCachedBuffer(mem);
+      });
+      return mem;
+    },
+
+    /**
+     * void UnmapTexSubImage2DCHROMIUM([in] PP_Resource context,
+     *                                 [in] mem_t mem);
+     */
+    PPB_OpenGLES2ChromiumMapSub_UnmapTexSubImage2DCHROMIUM: function(json) {
+      let graphics = PP_Resource.lookup(json.context);
+      let context = graphics.context;
+      let mappedTexture = graphics.mappedTextures.get(json.mem);
+      graphics.mappedTextures.delete(json.mem);
+      mappedTexture();
+    },
+
+
+    /**
+     * PP_Resource GetFontFileWithFallback([in] PP_Instance instance,
+     *                                     [in] PP_BrowserFont_Trusted_Description description,
+     *                                     [in] PP_PrivateFontCharset charset);
+     */
+    PPB_PDF_GetFontFileWithFallback: function(json) {
+      return 0;
+    },
+
+    /**
+     * void SearchString(
+     *   [in] PP_Instance instance,
+     *   [in] mem_t str,
+     *   [in] mem_t term,
+     *   [in] PP_Bool case_sensitive,
+     *   [out, size_is(count)] PP_PrivateFindResult[] results,
+     *   [out] int32_t count);
+     */
+    PPB_PDF_SearchString: function(json) {
+      // Retrieve data from buffer
+      let tmp = "";
+      let data = "";
+      let term = "";
+      let i = 0;
+      while (1) {
+        tmp = this.copyBuffer(json.str + i * 2, 2);
+        if ((new Uint16Array(tmp))[0] == 0) {
+          tmp = this.copyBuffer(json.str, i * 2);
+          data = new TextDecoder("utf-16").decode(tmp);
+          break;
+        }
+        ++i;
+      }
+
+      // Retrieve term from buffer
+      i = 0;
+      while (1) {
+        tmp = this.copyBuffer(json.term + i * 2, 2);
+        if ((new Uint16Array(tmp))[0] == 0) {
+          tmp = this.copyBuffer(json.term, i * 2);
+          term = new TextDecoder("utf-16").decode(tmp);
+          break;
+        }
+        ++i;
+      }
+
+      // FIXME Should use ICU to search string
+      let results = [];
+      let n = -1;
+      if (PP_Bool[json.case_sensitive] == PP_Bool.PP_FALSE) {
+        // case-insensitive
+        data = data.toLowerCase();
+        term = term.toLowerCase();
+      }
+      while ((n = data.indexOf(term, n + 1)) != -1) {
+        results.push({start_index: n, length: term.length});
+      }
+      return [{results, count: results.length}];
+    },
+
+    /*
+     * void SetSelectedText(
+     *     [in] PP_Instance instance,
+     *     [in] str_t selected_text);
+     */
+    PPB_PDF_SetSelectedText: function(json) {
+      let instance = this.instances[json.instance];
+      instance.selectedText = json.selected_text;
+    },
+
+
+    /**
+     * PP_Resource Create([in] PP_Instance instance);
+     */
+    PPB_Printing_Dev_Create: function(json) {
+      return new PrintingDev(this.instances[json.instance]);
+    },
+
+
+    /**
+     * PP_Resource Create([in] PP_Instance instance);
+     */
+    PPB_TCPSocket_Private_Create: function(json) {
+      return new TCPSocketPrivate(this.instances[json.instance]);
+    },
+
+    /**
+     * int32_t Connect([in] PP_Resource tcp_socket,
+     *                 [in] str_t host,
+     *                 [in] uint16_t port,
+     *                 [in] PP_CompletionCallback callback);
+     */
+    PPB_TCPSocket_Private_Connect: function(json) {
+      let socket = PP_Resource.lookup(json.tcp_socket);
+      socket.connect(json.host, json.port, json.callback);
+      return PP_OK_COMPLETIONPENDING;
+    },
+
+    /**
+     * PP_Bool GetLocalAddress([in] PP_Resource tcp_socket,
+     *                         [out] PP_NetAddress_Private local_addr);
+     */
+    PPB_TCPSocket_Private_GetLocalAddress: function(json) {
+      let socket = PP_Resource.lookup(json.tcp_socket);
+      let localAddr = socket.localAddress;
+      if (!localAddr) {
+        return [PP_Bool.PP_FALSE, { local_addr: null }];
+      }
+      return [PP_Bool.PP_FALSE, { local_addr: { size: localAddr.length, data: localAddr } }];
+    },
+
+    /**
+     * PP_Bool GetRemoteAddress([in] PP_Resource tcp_socket,
+     *                          [out] PP_NetAddress_Private remote_addr);
+     */
+    PPB_TCPSocket_Private_GetRemoteAddress: function(json) {
+      let socket = PP_Resource.lookup(json.tcp_socket);
+      let remoteAddr = socket.remoteAddress;
+      if (!remoteAddr) {
+        return [PP_Bool.PP_FALSE, { remote_addr: null }];
+      }
+      return [PP_Bool.PP_FALSE, { remote_addr: { size: remoteAddr.length, data: remoteAddr } }];
+    },
+
+    /**
+     * int32_t Read([in] PP_Resource tcp_socket,
+     *              [out] str_t buffer,
+     *              [in] int32_t bytes_to_read,
+     *              [in] PP_CompletionCallback callback);
+     */
+    PPB_TCPSocket_Private_Read: function(json) {
+      let socket = PP_Resource.lookup(json.tcp_socket);
+      socket.read(json.buffer, json.bytes_to_read, json.callback);
+      return [PP_OK_COMPLETIONPENDING, { buffer: "" }];
+    },
+
+    /**
+     * int32_t Write([in] PP_Resource tcp_socket,
+     *               [in] str_t buffer,
+     *               [in] int32_t bytes_to_write,
+     *               [in] PP_CompletionCallback callback);
+     */
+    PPB_TCPSocket_Private_Write: function(json) {
+      let socket = PP_Resource.lookup(json.tcp_socket);
+      if (!socket.write(json.buffer)) {
+        return PP_ERROR_FAILED;
+      }
+      socket.instance.rt.call(new CallbackCall("PP_CompletionCallback", json.callback, { result: json.bytes_to_write }));
+      return PP_OK_COMPLETIONPENDING;
+    },
+
+    /**
+     * void Disconnect([in] PP_Resource tcp_socket);
+     */
+    PPB_TCPSocket_Private_Disconnect: function(json) {
+      let socket = PP_Resource.lookup(json.tcp_socket).impl;
+      socket.close();
+    },
+
+
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance);
+     */
+    PPB_URLLoader_Create: function(json) {
+      return new URLLoader(this.instances[json.instance]);
+    },
+
+    /**
+     * int32_t Open(
+     *     [in] PP_Resource loader,
+     *     [in] PP_Resource request_info,
+     *     [in] PP_CompletionCallback callback);
+     */
+    PPB_URLLoader_Open: function(json) {
+      let loader = PP_Resource.lookup(json.loader);
+      let requestInfo = PP_Resource.lookup(json.request_info);
+      loader.openURL(requestInfo.getProperty(PP_URLRequestProperty.PP_URLREQUESTPROPERTY_METHOD),
+                     requestInfo.getProperty(PP_URLRequestProperty.PP_URLREQUESTPROPERTY_URL),
+                     (result) => { loader.instance.rt.call(new CallbackCall("PP_CompletionCallback", json.callback, { result: result })); });
+      return PP_OK_COMPLETIONPENDING;
+    },
+
+    /**
+     * [always_set_output_parameters]
+     * PP_Bool GetDownloadProgress(
+     *     [in] PP_Resource loader,
+     *     [out] int64_t bytes_received,
+     *     [out] int64_t total_bytes_to_be_received);
+     */
+    PPB_URLLoader_GetDownloadProgress: function(json) {
+      let loader = PP_Resource.lookup(json.loader);
+      return [PP_Bool.PP_TRUE, { bytes_received: loader.bytes_received, total_bytes_to_be_received: loader.total_bytes_to_be_received }];
+    },
+
+    /**
+     * PP_Resource GetResponseInfo(
+     *     [in] PP_Resource loader);
+     */
+    PPB_URLLoader_GetResponseInfo: function(json) {
+      return PP_Resource.lookup(json.loader).responseInfo;
+    },
+
+    /**
+     * int32_t ReadResponseBody(
+     *     [in] PP_Resource loader,
+     *     [out] mem_t buffer,
+     *     [in] int32_t bytes_to_read,
+     *     [in] PP_CompletionCallback callback);
+     */
+    PPB_URLLoader_ReadResponseBody: function(json) {
+      let loader = PP_Resource.lookup(json.loader);
+      let bytesRead = loader.readResponse(json.buffer,
+                                          json.bytes_to_read,
+                                          (bytesRead) => loader.instance.rt.call(new CallbackCall("PP_CompletionCallback", json.callback, { result: bytesRead })));
+      // FIXME Why is buffer an out param?
+      return [bytesRead < 0 ? PP_OK_COMPLETIONPENDING : bytesRead, { buffer: json.buffer }];
+    },
+
+
+    /**
+     * void GrantUniversalAccess([in] PP_Resource loader);
+     */
+    PPB_URLLoaderTrusted_GrantUniversalAccess: function(json) {
+      PP_Resource.lookup(json.loader).grantedUniversalAccess = true;
+    },
+
+
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance);
+     */
+    PPB_URLRequestInfo_Create: function(json) {
+      return new URLRequestInfo(this.instances[json.instance]);
+    },
+
+    /**
+     * PP_Bool SetProperty(
+     *     [in] PP_Resource request,
+     *     [in] PP_URLRequestProperty property,
+     *     [in] PP_Var value);
+     */
+    PPB_URLRequestInfo_SetProperty: function(json) {
+      let request = PP_Resource.lookup(json.request);
+      let value;
+      let type = PP_VarType[json.value.type];
+      let property = PP_URLRequestProperty[json.property];
+      switch (property) {
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_CUSTOMREFERRERURL:
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_CUSTOMCONTENTTRANSFERENCODING:
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_CUSTOMUSERAGENT:
+          if (type == PP_VarType.PP_VARTYPE_UNDEFINED) {
+            value = undefined;
+            break;
+          }
+          /* falls through */
+
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_URL:
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_METHOD:
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_HEADERS:
+          if (type != PP_VarType.PP_VARTYPE_STRING) {
+            throw Error("Expected string resource property");
+          }
+          value = String_PP_Var.getAsJSValue(json.value);
+          break;
+
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_STREAMTOFILE:
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_FOLLOWREDIRECTS:
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_RECORDDOWNLOADPROGRESS:
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_RECORDUPLOADPROGRESS:
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS:
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_ALLOWCREDENTIALS:
+          if (type != PP_VarType.PP_VARTYPE_BOOL) {
+            throw Error("Expected boolean resource property");
+          }
+          value = Bool_PP_Var.getAsJSValue(json.value);
+          break;
+
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_PREFETCHBUFFERUPPERTHRESHOLD:
+        case PP_URLRequestProperty.PP_URLREQUESTPROPERTY_PREFETCHBUFFERLOWERTHRESHOLD:
+          if (type != PP_VarType.PP_VARTYPE_INT32) {
+            throw Error("Expected integer resource property");
+          }
+          value = Int32_PP_Var.getAsJSValue(json.value);
+          break;
+      }
+
+      request.setProperty(property, value);
+      return PP_Bool.PP_TRUE;
+    },
+
+
+    /**
+     * PP_Var GetProperty(
+     *     [in] PP_Resource response,
+     *     [in] PP_URLResponseProperty property);
+     */
+    PPB_URLResponseInfo_GetProperty: function(json) {
+      let response = PP_Resource.lookup(json.response);
+      return response.getProperty(PP_URLResponseProperty[json.property]);
+    },
+
+
+    /**
+     * PP_Var GetDocumentURL([in] PP_Instance instance,
+     *                       [out] PP_URLComponents_Dev components);
+     */
+    PPB_URLUtil_Dev_GetDocumentURL: function(json) {
+      var url = this.instances[json.instance].info.documentURL;
+      return [new String_PP_Var(url), { components: this.parseURL(url) }];
+    },
+
+    /**
+     * PP_Var GetPluginInstanceURL([in] PP_Instance instance,
+     *                             [out] PP_URLComponents_Dev components);
+     */
+    PPB_URLUtil_Dev_GetPluginInstanceURL: function(json) {
+      var url = this.instances[json.instance].info.url;
+      return [new String_PP_Var(url), { components: this.parseURL(url) }];
+    },
+
+
+    /**
+     * void AddRef([in] PP_Var var);
+     */
+    PPB_Var_AddRef: function(json) {
+      json.var.type = PP_VarType[json.var.type];
+      PP_Var_Cached.addRef(json.var);
+    },
+
+    /**
+     * void Release([in] PP_Var var);
+     */
+    PPB_Var_Release: function(json) {
+      json.var.type = PP_VarType[json.var.type];
+      PP_Var_Cached.release(json.var);
+    },
+
+    /**
+     * PP_Var VarFromUtf8([in] str_t data, [in] uint32_t len);
+     */
+    PPB_Var_VarFromUtf8: function(json) {
+      return new String_PP_Var(json.data);
+    },
+
+    /**
+     * str_t VarToUtf8([in] PP_Var var, [out] uint32_t len);
+     */
+    PPB_Var_VarToUtf8: function(json) {
+      if (PP_VarType[json.var.type] != PP_VarType.PP_VARTYPE_STRING) {
+        return [null, { len: 0 }];
+      }
+      let data = String_PP_Var.getAsJSValue(json.var);
+      let converted = new TextEncoder().encode(data);
+      return [data, { len: converted.length }];
+    },
+
+
+    /**
+     * PP_Bool IsInstanceOf([in] PP_Var var,
+     *                      [in, ref] PPP_Class_Deprecated object_class,
+     *                      [out] mem_t object_data);
+     */
+    PPB_Var_Deprecated_IsInstanceOf: function(json) {
+      let [object, instance] = Object_PP_Var.getAsJSValueWithInstance(json.var);
+      let [isInstanceOf, object_data] = instance.mm.sendRpcMessage("ppapiflash.js:isInstanceOf", { objectClass: json.object_class }, { object, instance })[0];
+      if (!isInstanceOf) {
+        return PP_Bool.PP_FALSE;
+      }
+      return [PP_Bool.PP_TRUE, { object_data }];
+    },
+
+    /**
+     * PP_Var CreateObject([in] PP_Instance instance,
+     *                     [in, ref] PPP_Class_Deprecated object_class,
+     *                     [inout] mem_t object_data);
+     */
+    PPB_Var_Deprecated_CreateObject: function(json) {
+      let instance = this.instances[json.instance];
+      let call = (name, args) => {
+        let callObj = { __interface: "PPP_Class_Deprecated;1.0", __instance: json.object_class, __member: name, object: json.object_data };
+        args = JSON.parse(args);
+        if (args) {
+          args.entries().map((name, value) => {
+            callObj[name] = PP_Var.fromJSValue(value, instance);
+          });
+        }
+        return this.call(callObj, true);
+      };
+      return instance.mm.sendRpcMessage("ppapiflash.js:createObject", { objectClass: json.object_class, objectData: json.object_data }, { instance, call })[0];
+    },
+
+    /**
+     * PP_Var GetProperty([in] PP_Var object,
+     *                    [in] PP_Var name,
+     *                    [out] PP_Var exception);
+     */
+    PPB_Var_Deprecated_GetProperty: function(json) {
+      let [object, instance] = Object_PP_Var.getAsJSValueWithInstance(json.object);
+      let name = String_PP_Var.getAsJSValue(json.name);
+      return instance.mm.sendRpcMessage("ppapiflash.js:getProperty", { name }, { object, instance })[0];
+    },
+
+
+    /**
+     * PP_Var Create();
+     */
+    PPB_VarArray_Create: function(json) {
+      return new Array_PP_Var();
+    },
+
+    /**
+     * PP_Var Get([in] PP_Var array, [in] uint32_t index);
+     */
+    PPB_VarArray_Get: function(json) {
+      let array = Array_PP_Var.getAsJSValue(json.dict);
+      if (json.index >= array.length) {
+        return new PP_Var();
+      }
+      let value = array[json.index];
+      PP_Var_Cached.addRef(value);
+      return PP_Var.normalize(value);
+    },
+
+    /**
+     * PP_Bool Set([in] PP_Var array, [in] uint32_t index, [in] PP_Var value);
+     */
+    PPB_VarArray_Set: function(json) {
+      json.value.type = PP_VarType[json.value.type];
+      Array_PP_Var.getAsJSValue(json.array)[json.index] = json.value;
+      PP_Var_Cached.addRef(json.value);
+      return PP_Bool.PP_TRUE;
+    },
+
+    /**
+     * uint32_t GetLength([in] PP_Var array);
+     */
+    PPB_VarArray_GetLength: function(json) {
+      return Array_PP_Var.getAsJSValue(json.array).length;
+    },
+
+    /**
+     * PP_Var Create();
+     */
+    PPB_VarDictionary_Create: function(json) {
+      return new Dictionary_PP_Var();
+    },
+
+    /**
+     * PP_Var Get([in] PP_Var dict, [in] PP_Var key);
+     */
+    PPB_VarDictionary_Get: function(json) {
+      let value = Dictionary_PP_Var.getAsJSValue(json.dict)[String_PP_Var.getAsJSValue(json.key)];
+      if (value === undefined) {
+        return new PP_Var();
+      }
+      PP_Var_Cached.addRef(value);
+      return PP_Var.normalize(value);
+    },
+
+    /**
+     * PP_Bool Set([in] PP_Var dict, [in] PP_Var key, [in] PP_Var value);
+     */
+    PPB_VarDictionary_Set: function(json) {
+      json.value.type = PP_VarType[json.value.type];
+      Dictionary_PP_Var.getAsJSValue(json.dict)[String_PP_Var.getAsJSValue(json.key)] = json.value;
+      PP_Var_Cached.addRef(json.value);
+      return PP_Bool.PP_TRUE;
+    },
+
+
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance);
+     */
+    PPB_VideoCapture_Dev_Create: function(json) {
+      return 0;
+    },
+
+    /**
+     * int32_t EnumerateDevices(
+     *     [in] PP_Resource video_capture,
+     *     [in] PP_ArrayOutput output,
+     *     [in] PP_CompletionCallback callback);
+     */
+    PPB_VideoCapture_Dev_EnumerateDevices: function(json) {
+      return PP_ERROR_BADRESOURCE;
+    },
+
+
+    /**
+     * PP_Resource Create(
+     *     [in] PP_Instance instance,
+     *     [in] PP_Resource context,
+     *     [in] PP_VideoDecoder_Profile profile);
+     */
+    PPB_VideoDecoder_Dev_Create: function(json) {
+      return 0;
+    },
+
+
+    /**
+     * PP_Bool GetRect([in] PP_Resource resource,
+     *                 [out] PP_Rect rect);
+     */
+    PPB_View_GetRect: function(json) {
+      let view = PP_Resource.lookup(json.resource);
+      let rect = view.instance.boundingRect;
+      return [PP_Bool.PP_TRUE, { rect: { point: { x: rect.left, y: rect.top }, size: { width: rect.width, height: rect.height } } }];
+    },
+
+    /**
+     * PP_Bool GetClipRect([in] PP_Resource resource,
+     *                     [out] PP_Rect clip);
+     */
+    PPB_View_GetClipRect: function(json) {
+      let view = PP_Resource.lookup(json.resource);
+      let point = { x: 0, y: 0 };
+      let size;
+      if (view.instance.throttled) {
+        size = { width: 0, height: 0 };
+      } else {
+        let rect = view.instance.boundingRect;
+        size = { width: rect.width, height: rect.height };
+      }
+      return [PP_Bool.PP_TRUE, { rect: { point, size } }];
+    },
+
+    /**
+     * float_t GetDeviceScale([in] PP_Resource resource);
+     */
+    PPB_View_GetDeviceScale: function(json) {
+      // FIXME Need to figure out how to get the ratio between device pixels
+      //       and DIPs.
+      let view = PP_Resource.lookup(json.resource);
+      return 1; //view.instance.window.devicePixelRatio;
+    },
+
+    /**
+     * float_t GetCSSScale([in] PP_Resource resource);
+     */
+    PPB_View_GetCSSScale: function(json) {
+      // FIXME Need to figure out how to get the ratio between CSS pixels
+      //       and DIPs.
+      let view = PP_Resource.lookup(json.resource);
+      return view.instance.window.devicePixelRatio;
+    },
+
+    /**
+     * PP_Bool GetScrollOffset([in] PP_Resource resource,
+     *                         [out] PP_Point offset);
+     */
+    PPB_View_GetScrollOffset: function(json) {
+      let view = PP_Resource.lookup(json.resource);
+      let position = view.instance.viewport.getScrollOffset();
+      return [ PP_Bool.PP_TRUE, { offset: position }];
+    },
+
+    /**
+     * float_t GetDeviceScale([in] PP_Resource resource);
+     */
+    PPB_View_Dev_GetDeviceScale: function(json) {
+      let view = PP_Resource.lookup(json.resource);
+      return view.instance.window.devicePixelRatio;
+    },
+
+    /**
+     * PP_Bool IsWheelInputEvent([in] PP_Resource resource);
+     */
+    PPB_WheelInputEvent_IsWheelInputEvent: function(json) {
+      let resource = PP_Resource.lookup(json.resource);
+      return resource instanceof WheelInputEvent ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE;
+    },
+
+    /**
+     * PP_FloatPoint GetTicks([in] PP_Resource wheel_event);
+     */
+    PPB_WheelInputEvent_GetTicks: function(json) {
+      let event = PP_Resource.lookup(json.wheel_event);
+      return { x: event.domEvent.deltaX, y: event.domEvent.deltaY };
+    },
+
+    /**
+     * PP_Bool GetScrollByPage([in] PP_Resource wheel_event);
+     */
+    PPB_WheelInputEvent_GetScrollByPage: function(json) {
+      let event = PP_Resource.lookup(json.wheel_event);
+      return event.domEvent.deltaMode == event.domEvent.DOM_DELTA_PAGE
+        ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE;
+    },
+  },
+};
+
+var EXPORTED_SYMBOLS = ["PPAPIRuntime"];
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/flash/bootstrap.js
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function sandboxScript(sandbox)
+{
+  dump("sandboxScript " + sandbox.pluginElement + "\n");
+  Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader).loadSubScript("resource://ppapiflash.js/ppapi-content-sandbox.js", sandbox);
+}
+
+let plugins;
+function startup(data) {
+  dump(">>>STARTED!!!\n");
+
+  let root = data.installPath.parent.parent;
+  let rpclib = root.clone();
+  let pluginlib = root.clone();
+  let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+  if (os == "Darwin") {
+    rpclib.appendRelativePath("ppapi/out/rpc.dylib");
+    pluginlib.appendRelativePath("plugin/PepperFlashPlayer-debug");
+  } else if (os == "Linux") {
+    rpclib.appendRelativePath("ppapi/out/rpc.so");
+    pluginlib.appendRelativePath("plugin/libpepflashplayer.so");
+  } else if (os == "WINNT") {
+    rpclib.appendRelativePath("ppapi\\out\\rpc.dll");
+    pluginlib.appendRelativePath("plugin\\pepflashplayer.dll");
+  } else {
+    throw("Don't know the path to the libraries for this OS!");
+  }
+  rpclib = rpclib.path;
+  pluginlib = pluginlib.path;
+
+  let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+  let plugin = pluginHost.registerFakePlugin({
+    handlerURI: "chrome://ppapiflash.js/content/viewer.html",
+    mimeEntries: [{ type: "application/x-shockwave-flash", extension: "swf" }],
+    name: "Shockwave Flash",
+    niceName: "PPAPI Flash plugin",
+    description: "10",
+    version: "1.0",
+    sandboxScript : `(${sandboxScript.toSource()})(this);`,
+    ppapiProcessArgs: [ rpclib, pluginlib ],
+  });
+  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+
+  let rng = Cc["@mozilla.org/security/random-generator;1"].createInstance(Ci.nsIRandomGenerator);
+  Services.ppmm.addMessageListener("ppapi.js:generateRandomBytes", ({ data }) => {
+    return rng.generateRandomBytes(data);
+  });
+
+  let moduleLocalFiles = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
+  moduleLocalFiles.append("Flash PPAPI Data");
+  try {
+    moduleLocalFiles.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+  } catch (e) {
+    if (e.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
+      throw e;
+    }
+  }
+  Services.ppmm.addMessageListener("ppapiflash.js:getModuleLocalFilesPath", () => {
+    return moduleLocalFiles.path;
+  });
+
+  dump("<<<STARTED!!!\n");
+}
+
+function shutdown() {
+  dump("SHUTDOWN!!!\n");
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/flash/chrome.manifest
@@ -0,0 +1,3 @@
+resource ppapiflash.js .
+content ppapiflash.js chrome/
+resource ppapi.js ../common/
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/flash/chrome/viewer.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+      background-color: transparent;
+    }
+
+    canvas {
+      position: absolute;
+      left: 0;
+      top: 0;
+      bottom: 0;
+      right: 0;
+      overflow: hidden;
+      line-height: 0;
+      border: 0px none;
+    }
+  </style>
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/flash/install.rdf
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  This Source Code Form is subject to the terms of the Mozilla Public
+   -  License, v. 2.0. If a copy of the MPL was not distributed with this
+   -  file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>ppapiflash.js@mozilla.org</em:id>
+    <em:name>ppapiflash.js</em:name>
+    <em:description>ppapiflash.js</em:description>
+    <em:version>0.1</em:version>
+    <em:type>2</em:type>
+    <em:bootstrap>true</em:bootstrap>
+    <em:multiprocessCompatible>true</em:multiprocessCompatible>
+    <em:targetApplication>
+      <Description>
+        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!--Firefox-->
+        <em:minVersion>37.0</em:minVersion>
+        <em:maxVersion>45.*</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+    <em:strictCompatibility>false</em:strictCompatibility>
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/flash/ppapi-content-sandbox.js
@@ -0,0 +1,226 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This code runs in the sandbox in the content process where the page that
+ * loaded the plugin lives. It communicates with the process where the PPAPI
+ * implementation lives.
+ */
+const { utils: Cu } = Components;
+
+let mm = pluginElement.frameLoader.messageManager;
+let containerWindow = pluginElement.ownerDocument.defaultView;
+
+function mapValue(v, instance) {
+  return instance.rt.toPP_Var(v, instance);
+}
+
+dump("<>>>>>>>>>>>>>>>>>>>> AHA <<<<<<<<<<<<<<<<<<<<<>\n");
+dump(`pluginElement: ${pluginElement.toSource()}\n`);
+dump(`pluginElement.frameLoader: ${pluginElement.frameLoader.toSource()}\n`);
+dump(`pluginElement.frameLoader.messageManager: ${pluginElement.frameLoader.messageManager.toSource()}\n`);
+dump("<>>>>>>>>>>>>>>>>>>>> AHA2 <<<<<<<<<<<<<<<<<<<<<>\n");
+
+mm.addMessageListener("ppapi.js:frameLoaded", ({ target }) => {
+  let tagName = pluginElement.nodeName;
+
+  // Getting absolute URL from the EMBED tag
+  let url = pluginElement.srcURI.spec;
+  let objectParams = new Map();
+  for (let i = 0; i < pluginElement.attributes.length; ++i) {
+    let paramName = pluginElement.attributes[i].localName;
+    objectParams.set(paramName, pluginElement.attributes[i].value);
+  }
+  if (tagName == "OBJECT") {
+    let params = pluginElement.getElementsByTagName("param");
+    Array.prototype.forEach.call(params, (p) => {
+      var paramName = p.getAttribute("name").toLowerCase();
+      objectParams.set(paramName, p.getAttribute("value"));
+    });
+  }
+
+  let documentURL = pluginElement.ownerDocument.location.href;
+  let baseUrl = documentURL;
+  if (objectParams.base) {
+    try {
+      let parsedDocumentUrl = Services.io.newURI(documentURL);
+      baseUrl = Services.io.newURI(objectParams.base, null, parsedDocumentUrl).spec;
+    } catch (e) { /* ignore */ }
+  }
+
+  let info = {
+    url,
+    documentURL,
+    isFullFrame: pluginElement.ownerDocument.mozSyntheticDocument,
+    setupJSInstanceObject: true,
+    arguments: {
+      keys: Array.from(objectParams.keys()),
+      values: Array.from(objectParams.values()),
+    },
+  };
+
+  mm.sendAsyncMessage("ppapi.js:createInstance", { type: "flash", info },
+                      { pluginWindow: containerWindow });
+});
+
+mm.addMessageListener("ppapiflash.js:executeScript", ({ data, objects: { instance } }) => {
+  return mapValue(containerWindow.eval(data), instance);
+});
+
+let jsInterfaceObjects = new WeakMap();
+
+mm.addMessageListener("ppapiflash.js:createObject", ({ data: { objectClass, objectData }, objects: { instance, call: callRemote } }) => {
+  dump("ppapiflash.js:createObject\n");
+  let call = (name, args) => {
+/*
+    let metaData = target[Symbol.for("metaData")];
+    let callObj = { __interface: "PPP_Class_Deprecated;1.0", __instance: metaData.objectClass, __member: name, object: metaData.objectData };
+*/
+dump("ppapiflash.js:createObject -> call\n");
+try {
+    let result = callRemote(name, JSON.stringify(args));
+} catch (e) {
+  dump("FIXME: we rely on CPOWs!\n");
+  return undefined;
+}
+dump("RESULT: " + result[0] + "\n");
+dump("EXCEPTION: " + result[1] + "\n");
+    return result[0];
+  };
+
+  const handler = {
+    // getPrototypeOf -> target (see bug 888969)
+    // setPrototypeOf -> target (see bug 888969)
+    // isExtensible -> target
+    // preventExtensions -> target
+    getOwnPropertyDescriptor: function(target, name) {
+      let value = this.get(target, name);
+      if (this._hasProperty(target, name)) {
+        return {
+          "writable": true,
+          "enumerable": true,
+          "configurable": true,
+          "get": this.get.bind(this, target, name),
+          "set": this.set.bind(this, target, name),
+        };
+      }
+
+      if (this._hasMethod(target, name)) {
+        return {
+          "value": (...args) => {
+            //call("Call", { name, argc, argv, exception });
+          },
+          "writable": true,
+          "enumerable": true,
+          "configurable": true,
+        };
+      }
+
+      return undefined;
+    },
+    // defineProperty -> target
+    has: function(target, name) {
+      return this._hasProperty(target, name) || this._hasMethod(target, name);
+    },
+    get: function(target, name, receiver) {
+      dump(`Calling GetProperty for ${name.toSource()}\n`);
+      let prop = call("GetProperty", { name });
+      if (!prop || PP_VarType[prop.type] == PP_VarType.PP_VARTYPE_UNDEFINED) {
+        // FIXME Need to get the exception!
+        return undefined;
+      }
+      return prop;
+    },
+    set: function(target, name, value, receiver) {
+      call("SetProperty", { name, value });
+      // FIXME Need to get the exception!
+      return true;
+    },
+    deleteProperty: function(target, name) {
+      call("RemoveProperty", { name });
+      // FIXME Need to get the exception!
+      return true;
+    },
+    enumerate: function(target) {
+      let keys = this.ownKeys(target);
+      return keys[Symbol.iterator];
+    },
+    ownKeys: function(target) {
+      let result = call("GetAllPropertyNames");
+      dump(result.toSource() + "\n");
+      dump(typeof result.properties + "\n");
+      return result.properties;
+    },
+    apply: function(target, thisArg, args) {
+    },
+    construct: function(target, args) {
+      //call("Construct", { argc, argv, exception });
+    },
+
+    _hasProperty: function(target, name) {
+      return call("HasProperty", { name }) == 1 /* PP_Bool.PP_TRUE */;
+    },
+    _hasMethod: function(target, name) {
+      return call("HasMethod", { name }) == 1 /* PP_Bool.PP_TRUE */;
+    },
+  };
+
+  let metaData = {
+    objectClass,
+    objectData,
+  };
+
+  let clonedHandler = Cu.cloneInto(handler, containerWindow, { cloneFunctions: true });
+  let clonedMetaData = Cu.cloneInto(metaData, containerWindow, { cloneFunctions: true });
+  let target = Cu.createObjectIn(containerWindow);
+  target[Symbol.for("metaData")] = clonedMetaData;
+
+dump("IN CREATEOBJECT\n");
+  let proxy = new containerWindow.Proxy(target, clonedHandler);
+  jsInterfaceObjects.set(proxy, metaData);
+  let foo = mapValue(proxy.wrappedJSObject, instance);
+dump("CREATEDOBJECT: " + foo.toSource() + "\n");
+  return foo;
+});
+
+mm.addMessageListener("ppapiflash.js:isInstanceOf", ({ data: { objectClass }, objects: { object, instance } }) => {
+  let metaData = jsInterfaceObjects.get(object);
+  if (!metaData || metaData.objectClass != objectClass) {
+    return [false];
+  }
+  return [true, metaData.objectData];
+});
+
+mm.addMessageListener("ppapiflash.js:getProperty", ({ data: { name }, objects: { object, instance } }) => {
+  try {
+    return [mapValue(object[name], instance), { exception: null }];
+  } catch (e) {
+    return [mapValue(null, instance), { exception: mapValue(e, instance) }];
+  }
+});
+
+mm.addMessageListener("ppapiflash.js:log", ({ data }) => {
+  containerWindow.console.log(data);
+});
+
+mm.addMessageListener("ppapiflash.js:setInstancePrototype", ({ objects: { proto } }) => {
+  Object.setPrototypeOf(proto.wrappedJSObject, Object.getPrototypeOf(pluginElement.wrappedJSObject));
+  Object.setPrototypeOf(pluginElement.wrappedJSObject, proto.wrappedJSObject);
+});
+
+mm.addMessageListener("ppapi.js:isFullscreen", () => {
+  return containerWindow.document.fullscreenElement == pluginElement;
+});
+
+mm.addMessageListener("ppapi.js:setFullscreen", ({ data }) => {
+  if (data) {
+    pluginElement.requestFullscreen();
+  } else {
+    containerWindow.document.exitFullscreen();
+  }
+});
+
+mm.loadFrameScript("resource://ppapi.js/ppapi-instance.js", true);
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/interpose.cc
@@ -0,0 +1,335 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "../ppapi/out/rpc.cc"
+#include <dlfcn.h>
+
+#define REAL_PLUGIN_PATH "/Applications/Google Chrome.app/Contents/Versions/42.0.2311.135/Google Chrome Framework.framework/Internet Plug-Ins/PepperFlash/PepperFlashPlayer.plugin/Contents/MacOS/PepperFlashPlayer"
+//#define REAL_PLUGIN_PATH "/usr/lib/pepperflashplugin-nonfree/libpepflashplayer.so"
+
+static PPB_GetInterface _real_PPB_GetInterface;
+
+const void* RealGetInterface(const char* interfaceName) {
+  if (!strcmp(interfaceName, "PPB_Flash_File_FileRef;2.0")) {
+    interfaceName = "PPB_Flash_File_FileRef;2";
+  } else if (!strcmp(interfaceName, "PPB_Flash_File_ModuleLocal;3.0")) {
+    interfaceName = "PPB_Flash_File_ModuleLocal;3";
+  }
+  return _real_PPB_GetInterface(interfaceName);
+}
+const void* GetInterfaceForRPC(const char* interfaceName) {
+  const void* interface = gInterfaces[interfaceName];
+  //printf("GetInterfaceForRPC %s\n", interfaceName);
+  if (!interface) {
+    printf("MISSING INTERFACE %s\n", interfaceName);
+  }
+  return interface;
+}
+
+void
+Fail(const char *reason, const char *data)
+{
+  fprintf(stdout, reason, data);
+  fflush(stdout);
+  exit(-1);
+}
+
+void Logging_PP_CompletionCallback(void* user_data, int32_t result)
+{
+  PP_CompletionCallback* _real_PP_CompletionCallback = static_cast<PP_CompletionCallback*>(user_data);
+  printf("callFromJSON: > {\"__callback\":\"PP_CompletionCallback\",\"__callbackStruct\":{\"func\":%lu,\"user_data\":%lu,\"flags\":%i},\"result\":%i}\n",
+         uintptr_t(_real_PP_CompletionCallback->func), uintptr_t(_real_PP_CompletionCallback->user_data), _real_PP_CompletionCallback->flags, result);
+  _real_PP_CompletionCallback->func(_real_PP_CompletionCallback->user_data, result);
+  printf("callFromJSON: < {\"__callback\":\"PP_CompletionCallback\",\"__callbackStruct\":{\"func\":%lu,\"user_data\":%lu,\"flags\":%i},\"result\":%i}\n",
+         uintptr_t(_real_PP_CompletionCallback->func), uintptr_t(_real_PP_CompletionCallback->user_data), _real_PP_CompletionCallback->flags, result);
+  delete _real_PP_CompletionCallback;
+}
+
+void Logging_PPB_Audio_Callback_1_0(void* sample_buffer,
+                                    uint32_t buffer_size_in_bytes,
+                                    void* user_data)
+{
+  Logging_PPB_Audio_Callback_1_0_holder* holder = static_cast<Logging_PPB_Audio_Callback_1_0_holder*>(user_data);
+  printf("callFromJSON: > {\"__callback\":\"PPB_Audio_Callback_1_0\",\"__callbackStruct\":{\"func\":%lu,\"sample_buffer\":%lu,\"buffer_size_in_bytes\":%i},\"user_data\":%lu}\n",
+         uintptr_t(holder->func), uintptr_t(sample_buffer), buffer_size_in_bytes, uintptr_t(holder->user_data));
+  holder->func(sample_buffer, buffer_size_in_bytes, holder->user_data);
+  printf("callFromJSON: < {\"__callback\":\"PPB_Audio_Callback_1_0\",\"__callbackStruct\":{\"func\":%lu,\"sample_buffer\":%lu,\"buffer_size_in_bytes\":%i},\"user_data\":%lu}\n",
+         uintptr_t(holder->func), uintptr_t(sample_buffer), buffer_size_in_bytes, uintptr_t(holder->user_data));
+}
+
+static void *_real_PepperFlash = nullptr;
+
+typedef int32_t (*PPP_InitializeBroker_Func)(PP_ConnectInstance_Func* connect_instance_func);
+typedef void (*PPP_ShutdownBroker_Func)(void);
+
+static PP_InitializeModule_Func _real_PPP_InitializeModule;
+static PP_GetInterface_Func _real_PPP_GetInterface;
+static PPP_InitializeBroker_Func _real_PPP_InitializeBroker;
+static PPP_ShutdownBroker_Func _real_PPP_ShutdownBroker;
+
+static void
+LoadRealPepperFlash()
+{
+  if (!_real_PepperFlash) {
+    _real_PepperFlash = dlopen(REAL_PLUGIN_PATH, RTLD_LAZY);
+    _real_PPP_InitializeModule = (PP_InitializeModule_Func)dlsym(_real_PepperFlash, "PPP_InitializeModule");
+    _real_PPP_GetInterface = (PP_GetInterface_Func)dlsym(_real_PepperFlash, "PPP_GetInterface");
+    _real_PPP_InitializeBroker = (PPP_InitializeBroker_Func)dlsym(_real_PepperFlash, "PPP_InitializeBroker");
+    _real_PPP_ShutdownBroker = (PPP_ShutdownBroker_Func)dlsym(_real_PepperFlash, "PPP_ShutdownBroker");
+    InitializeInterfaceList();
+  }
+}
+
+struct Logging_PPP_Class_Deprecated_holder;
+
+PP_Bool
+Logging_HasProperty(const void* object,
+                    PP_Var name,
+                    PP_Var* exception)
+{
+  uint32_t varNameLength;
+  const char* varName = ((PPB_Var_Deprecated_0_3*)RealGetInterface("PPB_Var(Deprecated);0.3"))->VarToUtf8(name, &varNameLength);
+  printf("Logging_HasProperty for ");
+  for (uint32_t i = 0; i < varNameLength; ++i) {
+    printf("%c", varName[i]);
+  }
+  printf("\n");
+  const Logging_PPP_Class_Deprecated_holder* holder = static_cast<const Logging_PPP_Class_Deprecated_holder*>(object);
+  PP_Bool result = holder->_real_PPP_Class_Deprecated->HasProperty(holder->object, name, exception);
+  printf("Logging_HasProperty for ");
+  for (uint32_t i = 0; i < varNameLength; ++i) {
+    printf("%c", varName[i]);
+  }
+  printf(" returns %s\n", result == PP_TRUE ? "true" : "false");
+  return result;
+}
+
+PP_Bool
+Logging_HasMethod(const void* object,
+                  PP_Var name,
+                  PP_Var* exception)
+{
+  uint32_t varNameLength;
+  const char* varName = ((PPB_Var_Deprecated_0_3*)RealGetInterface("PPB_Var(Deprecated);0.3"))->VarToUtf8(name, &varNameLength);
+  printf("Logging_HasMethod for ");
+  for (uint32_t i = 0; i < varNameLength; ++i) {
+    printf("%c", varName[i]);
+  }
+  printf("\n");
+  const Logging_PPP_Class_Deprecated_holder* holder = static_cast<const Logging_PPP_Class_Deprecated_holder*>(object);
+  PP_Bool result = holder->_real_PPP_Class_Deprecated->HasMethod(holder->object, name, exception);
+  printf("Logging_HasMethod for ");
+  for (uint32_t i = 0; i < varNameLength; ++i) {
+    printf("%c", varName[i]);
+  }
+  printf(" returns %s\n", result == PP_TRUE ? "true" : "false");
+  return result;
+}
+
+PP_Var
+Logging_GetProperty(const void* object,
+                    PP_Var name,
+                    PP_Var* exception)
+{
+  printf("Logging_GetProperty\n");
+  const Logging_PPP_Class_Deprecated_holder* holder = static_cast<const Logging_PPP_Class_Deprecated_holder*>(object);
+  return holder->_real_PPP_Class_Deprecated->GetProperty(holder->object, name, exception);
+}
+
+void
+Logging_GetAllPropertyNames(const void* object,
+                            uint32_t* property_count,
+                            PP_Var** properties,
+                            PP_Var* exception)
+{
+  printf("Logging_GetAllPropertyNames\n");
+  const Logging_PPP_Class_Deprecated_holder* holder = static_cast<const Logging_PPP_Class_Deprecated_holder*>(object);
+  holder->_real_PPP_Class_Deprecated->GetAllPropertyNames(holder->object, property_count, properties, exception);
+}
+
+void
+Logging_SetProperty(const void* object,
+                    PP_Var name,
+                    PP_Var value,
+                    PP_Var* exception)
+{
+  printf("Logging_SetProperty\n");
+  const Logging_PPP_Class_Deprecated_holder* holder = static_cast<const Logging_PPP_Class_Deprecated_holder*>(object);
+  holder->_real_PPP_Class_Deprecated->SetProperty(holder->object, name, value, exception);
+}
+
+void
+Logging_RemoveProperty(const void* object,
+                       PP_Var name,
+                       PP_Var* exception)
+{
+  printf("Logging_RemoveProperty\n");
+  const Logging_PPP_Class_Deprecated_holder* holder = static_cast<const Logging_PPP_Class_Deprecated_holder*>(object);
+  holder->_real_PPP_Class_Deprecated->RemoveProperty(holder->object, name, exception);
+}
+
+PP_Var
+Logging_Call(const void* object,
+             PP_Var method_name,
+             uint32_t argc,
+             const PP_Var argv[],
+             PP_Var* exception)
+{
+  printf("Logging_Call\n");
+  const Logging_PPP_Class_Deprecated_holder* holder = static_cast<const Logging_PPP_Class_Deprecated_holder*>(object);
+  return holder->_real_PPP_Class_Deprecated->Call(holder->object, method_name, argc, argv, exception);
+}
+
+PP_Var
+Logging_Construct(const void* object,
+                  uint32_t argc,
+                  const PP_Var argv[],
+                  PP_Var* exception)
+{
+  printf("Logging_Construct\n");
+  const Logging_PPP_Class_Deprecated_holder* holder = static_cast<const Logging_PPP_Class_Deprecated_holder*>(object);
+  return holder->_real_PPP_Class_Deprecated->Construct(holder->object, argc, argv, exception);
+}
+
+void
+Logging_Deallocate(const void* object)
+{
+  printf("Logging_Deallocate\n");
+  const Logging_PPP_Class_Deprecated_holder* holder = static_cast<const Logging_PPP_Class_Deprecated_holder*>(object);
+  holder->_real_PPP_Class_Deprecated->Deallocate(holder->object);
+  delete holder;
+}
+
+const PPP_Class_Deprecated _interpose_PPP_Class_Deprecated_1_0 = {
+  Logging_HasProperty,
+  Logging_HasMethod,
+  Logging_GetProperty,
+  Logging_GetAllPropertyNames,
+  Logging_SetProperty,
+  Logging_RemoveProperty,
+  Logging_Call,
+  Logging_Construct,
+  Logging_Deallocate,
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static PP_Bool
+Logging_HandleInputEvent(PP_Instance instance, PP_Resource input_event)
+{
+  const PPP_InputEvent_0_1* _real_PPP_InputEvent = static_cast<const PPP_InputEvent_0_1*>(_real_PPP_GetInterface("PPP_InputEvent;0.1"));
+  printf("callFromJSON: > {\"__interface\":\"PPP_InputEvent;0.1\",\"__member\":\"HandleInputEvent\",\"instance\":%i,\"input_event\":%i}\n", instance, input_event);
+  PP_Bool result = _real_PPP_InputEvent->HandleInputEvent(instance, input_event);
+  printf("callFromJSON: < \"%s\"\n", ToString_PP_Bool(result).c_str());
+  return result;
+}
+
+static const PPP_InputEvent_0_1 _interpose_PPP_InputEvent_0_1 = {
+  Logging_HandleInputEvent
+};
+
+static PP_Bool
+Logging_DidCreate(PP_Instance instance,
+                  uint32_t argc,
+                  const char* argn[],
+                  const char* argv[])
+{
+  const PPP_Instance_1_1* _real_PPP_Instance = static_cast<const PPP_Instance_1_1*>(_real_PPP_GetInterface("PPP_Instance;1.1"));
+  printf("callFromJSON: > {\"__interface\":\"PPP_Instance;1.1\",\"__member\":\"DidCreate\",\"instance\":%i,\"argc\":%i,\"argn\":[", instance, argc);
+  for (uint32_t i = 0; i < argc; ++i) {
+    if (i > 0) {
+      printf(",");
+    }
+    printf("\"%s\"", argn[i]);
+  }
+  printf("],\"argv\":[");
+  for (uint32_t i = 0; i < argc; ++i) {
+    if (i > 0) {
+      printf(",");
+    }
+    printf("\"%s\"", argv[i]);
+  }
+  printf("]}\n");
+  PP_Bool result = _real_PPP_Instance->DidCreate(instance, argc, argn, argv);
+  printf("callFromJSON: < \"%s\"\n", ToString_PP_Bool(result).c_str());
+  return result;
+}
+static void
+Logging_DidDestroy(PP_Instance instance)
+{
+  const PPP_Instance_1_1* _real_PPP_Instance = static_cast<const PPP_Instance_1_1*>(_real_PPP_GetInterface("PPP_Instance;1.1"));
+  printf("callFromJSON: > {\"__interface\":\"PPP_Instance;1.1\",\"__member\":\"DidDestroy\",\"instance\":%i}\n", instance);
+  _real_PPP_Instance->DidDestroy(instance);
+}
+static void
+Logging_DidChangeView(PP_Instance instance, PP_Resource view)
+{
+  const PPP_Instance_1_1* _real_PPP_Instance = static_cast<const PPP_Instance_1_1*>(_real_PPP_GetInterface("PPP_Instance;1.1"));
+  printf("callFromJSON: > {\"__interface\":\"PPP_Instance;1.1\",\"__member\":\"DidChangeView\",\"instance\":%i,\"view\":%i}\n", instance, view);
+  _real_PPP_Instance->DidChangeView(instance, view);
+}
+static void
+Logging_DidChangeFocus(PP_Instance instance, PP_Bool has_focus)
+{
+  const PPP_Instance_1_1* _real_PPP_Instance = static_cast<const PPP_Instance_1_1*>(_real_PPP_GetInterface("PPP_Instance;1.1"));
+  printf("callFromJSON: > {\"__interface\":\"PPP_Instance;1.1\",\"__member\":\"DidChangeFocus\",\"instance\":%i,\"has_focus\":%s}\n", instance, has_focus ? "PP_TRUE" : "PP_FALSE");
+  _real_PPP_Instance->DidChangeFocus(instance, has_focus);
+}
+static PP_Bool
+Logging_HandleDocumentLoad(PP_Instance instance, PP_Resource url_loader)
+{
+  const PPP_Instance_1_1* _real_PPP_Instance = static_cast<const PPP_Instance_1_1*>(_real_PPP_GetInterface("PPP_Instance;1.1"));
+  printf("callFromJSON: > {\"__interface\":\"PPP_Instance;1.1\",\"__member\":\"HandleDocumentLoad\",\"instance\":%i,\"url_loader\":%i}\n", instance, url_loader);
+  PP_Bool result = _real_PPP_Instance->HandleDocumentLoad(instance, url_loader);
+  printf("callFromJSON: < \"%s\"\n", ToString_PP_Bool(result).c_str());
+  return result;
+}
+
+static const PPP_Instance_1_1 _interpose_PPP_Instance_1_1 = {
+  Logging_DidCreate,
+  Logging_DidDestroy,
+  Logging_DidChangeView,
+  Logging_DidChangeFocus,
+  Logging_HandleDocumentLoad
+};
+
+const void *
+PPP_GetInterface(const char *interface_name)
+{
+//printf("PPP_GetInterface %s\n", interface_name);
+  LoadRealPepperFlash();
+  if (!strcmp(interface_name, "PPP_InputEvent;0.1")) {
+    return &_interpose_PPP_InputEvent_0_1;
+  }
+  if (!strcmp(interface_name, "PPP_Instance;1.1")) {
+    return &_interpose_PPP_Instance_1_1;
+  }
+  return _real_PPP_GetInterface(interface_name);
+}
+
+int32_t
+PPP_InitializeModule(PP_Module module, PPB_GetInterface get_browser_interface)
+{
+  LoadRealPepperFlash();
+  _real_PPB_GetInterface = get_browser_interface;
+  return _real_PPP_InitializeModule(module, GetInterfaceForRPC);
+}
+
+int32_t
+PPP_InitializeBroker(PP_ConnectInstance_Func *connect_instance_func)
+{
+  return _real_PPP_InitializeBroker(connect_instance_func);
+}
+
+void
+PPP_ShutdownBroker()
+{
+  return _real_PPP_ShutdownBroker();
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/pdf/bootstrap.js
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function sandboxScript(sandbox)
+{
+  dump("sandboxScript " + sandbox.pluginElement + "\n");
+  Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader).loadSubScript("resource://ppapipdf.js/ppapi-content-sandbox.js", sandbox);
+}
+
+let plugins;
+function startup(data) {
+  dump(">>>STARTED!!!\n");
+
+  let root = data.installPath.parent.parent;
+  let rpclib = root.clone();
+  let pluginlib = root.clone();
+  let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+  if (os == "Darwin") {
+    rpclib.appendRelativePath("ppapi/out/rpc.dylib");
+    pluginlib.appendRelativePath("plugin/libpepperpdfium.dylib");
+  } else if (os == "Linux") {
+    rpclib.appendRelativePath("ppapi/out/rpc.so");
+    pluginlib.appendRelativePath("plugin/libpepperpdfium.so");
+  } else if (os == "WINNT") {
+    rpclib.appendRelativePath("ppapi\\out\\rpc.dll");
+    pluginlib.appendRelativePath("plugin\\pepperpdfium.dll");
+  } else {
+    throw("Don't know the path to the libraries for this OS!");
+  }
+  rpclib = rpclib.path;
+  pluginlib = pluginlib.path;
+
+  let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+  let plugin = pluginHost.registerFakePlugin({
+    handlerURI: "chrome://ppapipdf.js/content/viewer.html",
+    mimeEntries: [
+      { type: "application/pdf", extension: "pdf" },
+      { type: "application/vnd.adobe.pdf", extension: "pdf" },
+      { type: "application/vnd.adobe.pdfxml", extension: "pdfxml" },
+      { type: "application/vnd.adobe.x-mars", extension: "mars" },
+      { type: "application/vnd.adobe.xdp+xml", extension: "xdp" },
+      { type: "application/vnd.adobe.xfdf", extension: "xfdf" },
+      { type: "application/vnd.adobe.xfd+xml", extension: "xfd" },
+      { type: "application/vnd.fdf", extension: "fdf" },
+    ],
+    name: "PPAPI PDF plugin",
+    niceName: "PPAPI PDF plugin",
+    version: "1.0",
+    sandboxScript : `(${sandboxScript.toSource()})(this);`,
+    ppapiProcessArgs: [ rpclib, pluginlib ],
+  });
+  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+
+  let rng = Cc["@mozilla.org/security/random-generator;1"].createInstance(Ci.nsIRandomGenerator);
+  Services.ppmm.addMessageListener("ppapi.js:generateRandomBytes", ({ data }) => {
+    return rng.generateRandomBytes(data);
+  });
+
+  dump("<<<STARTED!!!\n");
+}
+
+function shutdown() {
+  dump("SHUTDOWN!!!\n");
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/pdf/chrome.manifest
@@ -0,0 +1,3 @@
+resource ppapipdf.js .
+content ppapipdf.js chrome/
+resource ppapi.js ../common/
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/host/pdf/chrome/js/l20n.js
@@ -0,0 +1,2616 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+(function () { 'use strict';
+
+  function emit(listeners, ...args) {
+    const type = args.shift();
+
+    if (listeners['*']) {
+      listeners['*'].slice().forEach(
+        listener => listener.apply(this, args));
+    }
+
+    if (listeners[type]) {
+      listeners[type].slice().forEach(
+        listener => listener.apply(this, args));
+    }
+  }
+
+  function addEventListener(listeners, type, listener) {
+    if (!(type in listeners)) {
+      listeners[type] = [];
+    }
+    listeners[type].push(listener);
+  }
+
+  function removeEventListener(listeners, type, listener) {
+    const typeListeners = listeners[type];
+    const pos = typeListeners.indexOf(listener);
+    if (pos === -1) {
+      return;
+    }
+
+    typeListeners.splice(pos, 1);
+  }
+
+  class Client {
+    constructor(remote) {
+      this.id = this;
+      this.remote = remote;
+
+      const listeners = {};
+      this.on = (...args) => addEventListener(listeners, ...args);
+      this.emit = (...args) => emit(listeners, ...args);
+    }
+
+    method(name, ...args) {
+      return this.remote[name](...args);
+    }
+  }
+
+  function broadcast(type, data) {
+    Array.from(this.ctxs.keys()).forEach(
+      client => client.emit(type, data));
+  }
+
+  function L10nError(message, id, lang) {
+    this.name = 'L10nError';
+    this.message = message;
+    this.id = id;
+    this.lang = lang;
+  }
+  L10nError.prototype = Object.create(Error.prototype);
+  L10nError.prototype.constructor = L10nError;
+
+  const HTTP_STATUS_CODE_OK = 200;
+
+  function load(type, url) {
+    return new Promise((resolve, reject) => {
+      const xhr = new XMLHttpRequest();
+
+      if (xhr.overrideMimeType) {
+        xhr.overrideMimeType(type);
+      }
+
+      xhr.open('GET', url, true);
+
+      if (type === 'application/json') {
+        xhr.responseType = 'json';
+      }
+
+      xhr.addEventListener('load', e => {
+        if (e.target.status === HTTP_STATUS_CODE_OK ||
+            e.target.status === 0) {
+          resolve(e.target.response);
+        } else {
+          reject(new L10nError('Not found: ' + url));
+        }
+      });
+      xhr.addEventListener('error', reject);
+      xhr.addEventListener('timeout', reject);
+
+      // the app: protocol throws on 404, see https://bugzil.la/827243
+      try {
+        xhr.send(null);
+      } catch (e) {
+        if (e.name === 'NS_ERROR_FILE_NOT_FOUND') {
+          // the app: protocol throws on 404, see https://bugzil.la/827243
+          reject(new L10nError('Not found: ' + url));
+        } else {
+          throw e;
+        }
+      }
+    });
+  }
+
+  const io = {
+    extra: function(code, ver, path, type) {
+      return navigator.mozApps.getLocalizationResource(
+        code, ver, path, type);
+    },
+    app: function(code, ver, path, type) {
+      switch (type) {
+        case 'text':
+          return load('text/plain', path);
+        case 'json':
+          return load('application/json', path);
+        default:
+          throw new L10nError('Unknown file type: ' + type);
+      }
+    },
+  };
+
+  function fetchResource(res, { code, src, ver }) {
+    const url = res.replace('{locale}', code);
+    const type = res.endsWith('.json') ? 'json' : 'text';
+    return io[src](code, ver, url, type);
+  }
+
+  const KNOWN_MACROS = ['plural'];
+  const MAX_PLACEABLE_LENGTH = 2500;
+
+  // Unicode bidi isolation characters
+  const FSI = '\u2068';
+  const PDI = '\u2069';
+
+  const resolutionChain = new WeakSet();
+
+  function format(ctx, lang, args, entity) {
+    if (typeof entity === 'string') {
+      return [{}, entity];
+    }
+
+    if (resolutionChain.has(entity)) {
+      throw new L10nError('Cyclic reference detected');
+    }
+
+    resolutionChain.add(entity);
+
+    let rv;
+    // if format fails, we want the exception to bubble up and stop the whole
+    // resolving process;  however, we still need to remove the entity from the
+    // resolution chain
+    try {
+      rv = resolveValue(
+        {}, ctx, lang, args, entity.value, entity.index);
+    } finally {
+      resolutionChain.delete(entity);
+    }
+    return rv;
+  }
+
+  function resolveIdentifier(ctx, lang, args, id) {
+    if (KNOWN_MACROS.indexOf(id) > -1) {
+      return [{}, ctx._getMacro(lang, id)];
+    }
+
+    if (args && args.hasOwnProperty(id)) {
+      if (typeof args[id] === 'string' || (typeof args[id] === 'number' &&
+          !isNaN(args[id]))) {
+        return [{}, args[id]];
+      } else {
+        throw new L10nError('Arg must be a string or a number: ' + id);
+      }
+    }
+
+    // XXX: special case for Node.js where still:
+    // '__proto__' in Object.create(null) => true
+    if (id === '__proto__') {
+      throw new L10nError('Illegal id: ' + id);
+    }
+
+    const entity = ctx._getEntity(lang, id);
+
+    if (entity) {
+      return format(ctx, lang, args, entity);
+    }
+
+    throw new L10nError('Unknown reference: ' + id);
+  }
+
+  function subPlaceable(locals, ctx, lang, args, id) {
+    let newLocals, value;
+
+    try {
+      [newLocals, value] = resolveIdentifier(ctx, lang, args, id);
+    } catch (err) {
+      return [{ error: err }, FSI + '{{ ' + id + ' }}' + PDI];
+    }
+
+    if (typeof value === 'number') {
+      const formatter = ctx._getNumberFormatter(lang);
+      return [newLocals, formatter.format(value)];
+    }
+
+    if (typeof value === 'string') {
+      // prevent Billion Laughs attacks
+      if (value.length >= MAX_PLACEABLE_LENGTH) {
+        throw new L10nError('Too many characters in placeable (' +
+                            value.length + ', max allowed is ' +
+                            MAX_PLACEABLE_LENGTH + ')');
+      }
+      return [newLocals, FSI + value + PDI];
+    }
+
+    return [{}, FSI + '{{ ' + id + ' }}' + PDI];
+  }
+
+  function interpolate(locals, ctx, lang, args, arr) {
+    return arr.reduce(([localsSeq, valueSeq], cur) => {
+      if (typeof cur === 'string') {
+        return [localsSeq, valueSeq + cur];
+      } else {
+        const [, value] = subPlaceable(locals, ctx, lang, args, cur.name);
+        // wrap the substitution in bidi isolate characters
+        return [localsSeq, valueSeq + value];
+      }
+    }, [locals, '']);
+  }
+
+  function resolveSelector(ctx, lang, args, expr, index) {
+    //XXX: Dehardcode!!!
+    let selectorName;
+    if (index[0].type === 'call' && index[0].expr.type === 'prop' &&
+        index[0].expr.expr.name === 'cldr') {
+      selectorName = 'plural';
+    } else {
+      selectorName = index[0].name;
+    }
+    const selector = resolveIdentifier(ctx, lang, args, selectorName)[1];
+
+    if (typeof selector !== 'function') {
+      // selector is a simple reference to an entity or args
+      return selector;
+    }
+
+    const argValue = index[0].args ?
+      resolveIdentifier(ctx, lang, args, index[0].args[0].name)[1] : undefined;
+
+    if (selectorName === 'plural') {
+      // special cases for zero, one, two if they are defined on the hash
+      if (argValue === 0 && 'zero' in expr) {
+        return 'zero';
+      }
+      if (argValue === 1 && 'one' in expr) {
+        return 'one';
+      }
+      if (argValue === 2 && 'two' in expr) {
+        return 'two';
+      }
+    }
+
+    return selector(argValue);
+  }
+
+  function resolveValue(locals, ctx, lang, args, expr, index) {
+    if (!expr) {
+      return [locals, expr];
+    }
+
+    if (typeof expr === 'string' ||
+        typeof expr === 'boolean' ||
+        typeof expr === 'number') {
+      return [locals, expr];
+    }
+
+    if (Array.isArray(expr)) {
+      return interpolate(locals, ctx, lang, args, expr);
+    }
+
+    // otherwise, it's a dict
+    if (index) {
+      // try to use the index in order to select the right dict member
+      const selector = resolveSelector(ctx, lang, args, expr, index);
+      if (selector in expr) {
+        return resolveValue(locals, ctx, lang, args, expr[selector]);
+      }
+    }
+
+    // if there was no index or no selector was found, try the default
+    // XXX 'other' is an artifact from Gaia
+    const defaultKey = expr.__default || 'other';
+    if (defaultKey in expr) {
+      return resolveValue(locals, ctx, lang, args, expr[defaultKey]);
+    }
+
+    throw new L10nError('Unresolvable value');
+  }
+
+  /*eslint no-magic-numbers: [0]*/
+
+  const locales2rules = {
+    'af': 3,
+    'ak': 4,
+    'am': 4,
+    'ar': 1,
+    'asa': 3,
+    'az': 0,
+    'be': 11,
+    'bem': 3,
+    'bez': 3,
+    'bg': 3,
+    'bh': 4,
+    'bm': 0,
+    'bn': 3,
+    'bo': 0,
+    'br': 20,
+    'brx': 3,
+    'bs': 11,
+    'ca': 3,
+    'cgg': 3,
+    'chr': 3,
+    'cs': 12,
+    'cy': 17,
+    'da': 3,
+    'de': 3,
+    'dv': 3,
+    'dz': 0,
+    'ee': 3,
+    'el': 3,
+    'en': 3,
+    'eo': 3,
+    'es': 3,
+    'et': 3,
+    'eu': 3,
+    'fa': 0,
+    'ff': 5,
+    'fi': 3,
+    'fil': 4,
+    'fo': 3,
+    'fr': 5,
+    'fur': 3,
+    'fy': 3,
+    'ga': 8,
+    'gd': 24,
+    'gl': 3,
+    'gsw': 3,
+    'gu': 3,
+    'guw': 4,
+    'gv': 23,
+    'ha': 3,
+    'haw': 3,
+    'he': 2,
+    'hi': 4,
+    'hr': 11,
+    'hu': 0,
+    'id': 0,
+    'ig': 0,
+    'ii': 0,
+    'is': 3,
+    'it': 3,
+    'iu': 7,
+    'ja': 0,
+    'jmc': 3,
+    'jv': 0,
+    'ka': 0,
+    'kab': 5,
+    'kaj': 3,
+    'kcg': 3,
+    'kde': 0,
+    'kea': 0,
+    'kk': 3,
+    'kl': 3,
+    'km': 0,
+    'kn': 0,
+    'ko': 0,
+    'ksb': 3,
+    'ksh': 21,
+    'ku': 3,
+    'kw': 7,
+    'lag': 18,
+    'lb': 3,
+    'lg': 3,
+    'ln': 4,
+    'lo': 0,
+    'lt': 10,
+    'lv': 6,
+    'mas': 3,
+    'mg': 4,
+    'mk': 16,
+    'ml': 3,
+    'mn': 3,
+    'mo': 9,
+    'mr': 3,
+    'ms': 0,
+    'mt': 15,
+    'my': 0,
+    'nah': 3,
+    'naq': 7,
+    'nb': 3,
+    'nd': 3,
+    'ne': 3,
+    'nl': 3,
+    'nn': 3,
+    'no': 3,
+    'nr': 3,
+    'nso': 4,
+    'ny': 3,
+    'nyn': 3,
+    'om': 3,
+    'or': 3,
+    'pa': 3,
+    'pap': 3,
+    'pl': 13,
+    'ps': 3,
+    'pt': 3,
+    'rm': 3,
+    'ro': 9,
+    'rof': 3,
+    'ru': 11,
+    'rwk': 3,
+    'sah': 0,
+    'saq': 3,
+    'se': 7,
+    'seh': 3,
+    'ses': 0,
+    'sg': 0,
+    'sh': 11,
+    'shi': 19,
+    'sk': 12,
+    'sl': 14,
+    'sma': 7,
+    'smi': 7,
+    'smj': 7,
+    'smn': 7,
+    'sms': 7,
+    'sn': 3,
+    'so': 3,
+    'sq': 3,
+    'sr': 11,
+    'ss': 3,
+    'ssy': 3,
+    'st': 3,
+    'sv': 3,
+    'sw': 3,
+    'syr': 3,
+    'ta': 3,
+    'te': 3,
+    'teo': 3,
+    'th': 0,
+    'ti': 4,
+    'tig': 3,
+    'tk': 3,
+    'tl': 4,
+    'tn': 3,
+    'to': 0,
+    'tr': 0,
+    'ts': 3,
+    'tzm': 22,
+    'uk': 11,
+    'ur': 3,
+    've': 3,
+    'vi': 0,
+    'vun': 3,
+    'wa': 4,
+    'wae': 3,
+    'wo': 0,
+    'xh': 3,
+    'xog': 3,
+    'yo': 0,
+    'zh': 0,
+    'zu': 3
+  };
+
+  // utility functions for plural rules methods
+  function isIn(n, list) {
+    return list.indexOf(n) !== -1;
+  }
+  function isBetween(n, start, end) {
+    return typeof n === typeof start && start <= n && n <= end;
+  }
+
+  // list of all plural rules methods:
+  // map an integer to the plural form name to use
+  const pluralRules = {
+    '0': function() {
+      return 'other';
+    },
+    '1': function(n) {
+      if ((isBetween((n % 100), 3, 10))) {
+        return 'few';
+      }
+      if (n === 0) {
+        return 'zero';
+      }
+      if ((isBetween((n % 100), 11, 99))) {
+        return 'many';
+      }
+      if (n === 2) {
+        return 'two';
+      }
+      if (n === 1) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '2': function(n) {
+      if (n !== 0 && (n % 10) === 0) {
+        return 'many';
+      }
+      if (n === 2) {
+        return 'two';
+      }
+      if (n === 1) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '3': function(n) {
+      if (n === 1) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '4': function(n) {
+      if ((isBetween(n, 0, 1))) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '5': function(n) {
+      if ((isBetween(n, 0, 2)) && n !== 2) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '6': function(n) {
+      if (n === 0) {
+        return 'zero';
+      }
+      if ((n % 10) === 1 && (n % 100) !== 11) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '7': function(n) {
+      if (n === 2) {
+        return 'two';
+      }
+      if (n === 1) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '8': function(n) {
+      if ((isBetween(n, 3, 6))) {
+        return 'few';
+      }
+      if ((isBetween(n, 7, 10))) {
+        return 'many';
+      }
+      if (n === 2) {
+        return 'two';
+      }
+      if (n === 1) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '9': function(n) {
+      if (n === 0 || n !== 1 && (isBetween((n % 100), 1, 19))) {
+        return 'few';
+      }
+      if (n === 1) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '10': function(n) {
+      if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) {
+        return 'few';
+      }
+      if ((n % 10) === 1 && !(isBetween((n % 100), 11, 19))) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '11': function(n) {
+      if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
+        return 'few';
+      }
+      if ((n % 10) === 0 ||
+          (isBetween((n % 10), 5, 9)) ||
+          (isBetween((n % 100), 11, 14))) {
+        return 'many';
+      }
+      if ((n % 10) === 1 && (n % 100) !== 11) {
+        return 'one';
+      }
+      return 'other';
+    },
+    '12': function(n) {
+      if ((isBetween(n, 2, 4))) {
+        return 'few';
+      }
+      if