summaryrefslogtreecommitdiff
path: root/chromium/extensions/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/extensions/renderer')
-rw-r--r--chromium/extensions/renderer/BUILD.gn44
-rw-r--r--chromium/extensions/renderer/DEPS25
-rw-r--r--chromium/extensions/renderer/OWNERS2
-rw-r--r--chromium/extensions/renderer/activity_log_converter_strategy.cc83
-rw-r--r--chromium/extensions/renderer/activity_log_converter_strategy.h50
-rw-r--r--chromium/extensions/renderer/activity_log_converter_strategy_unittest.cc172
-rw-r--r--chromium/extensions/renderer/api/automation/automation_api_helper.cc90
-rw-r--r--chromium/extensions/renderer/api/automation/automation_api_helper.h34
-rw-r--r--chromium/extensions/renderer/api/display_source/OWNERS2
-rw-r--r--chromium/extensions/renderer/api/display_source/display_source_session.cc43
-rw-r--r--chromium/extensions/renderer/api/display_source/display_source_session.h116
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.cc166
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h122
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor_unittest.cc151
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc46
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h58
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.cc188
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h72
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.cc247
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h69
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc110
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h61
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc905
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.cc225
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.h95
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h43
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc727
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h176
-rw-r--r--chromium/extensions/renderer/api/mojo_private/mojo_private_unittest.cc59
-rw-r--r--chromium/extensions/renderer/api/serial/DEPS3
-rw-r--r--chromium/extensions/renderer/api/serial/data_receiver_unittest.cc202
-rw-r--r--chromium/extensions/renderer/api/serial/data_sender_unittest.cc213
-rw-r--r--chromium/extensions/renderer/api/serial/serial_api_unittest.cc738
-rw-r--r--chromium/extensions/renderer/api_activity_logger.cc82
-rw-r--r--chromium/extensions/renderer/api_activity_logger.h52
-rw-r--r--chromium/extensions/renderer/api_definitions_natives.cc37
-rw-r--r--chromium/extensions/renderer/api_definitions_natives.h34
-rw-r--r--chromium/extensions/renderer/api_test_base.cc240
-rw-r--r--chromium/extensions/renderer/api_test_base.h124
-rw-r--r--chromium/extensions/renderer/api_test_base_unittest.cc34
-rw-r--r--chromium/extensions/renderer/app_window_custom_bindings.cc82
-rw-r--r--chromium/extensions/renderer/app_window_custom_bindings.h32
-rw-r--r--chromium/extensions/renderer/binding_generating_native_handler.cc112
-rw-r--r--chromium/extensions/renderer/binding_generating_native_handler.h38
-rw-r--r--chromium/extensions/renderer/blob_native_handler.cc54
-rw-r--r--chromium/extensions/renderer/blob_native_handler.h30
-rw-r--r--chromium/extensions/renderer/console.cc127
-rw-r--r--chromium/extensions/renderer/console.h46
-rw-r--r--chromium/extensions/renderer/console_apitest.cc16
-rw-r--r--chromium/extensions/renderer/content_watcher.cc125
-rw-r--r--chromium/extensions/renderer/content_watcher.h73
-rw-r--r--chromium/extensions/renderer/context_menus_custom_bindings.cc32
-rw-r--r--chromium/extensions/renderer/context_menus_custom_bindings.h21
-rw-r--r--chromium/extensions/renderer/css_native_handler.cc36
-rw-r--r--chromium/extensions/renderer/css_native_handler.h28
-rw-r--r--chromium/extensions/renderer/dispatcher.cc1629
-rw-r--r--chromium/extensions/renderer/dispatcher.h318
-rw-r--r--chromium/extensions/renderer/dispatcher_delegate.h59
-rw-r--r--chromium/extensions/renderer/display_source_custom_bindings.cc306
-rw-r--r--chromium/extensions/renderer/display_source_custom_bindings.h63
-rw-r--r--chromium/extensions/renderer/document_custom_bindings.cc42
-rw-r--r--chromium/extensions/renderer/document_custom_bindings.h25
-rw-r--r--chromium/extensions/renderer/dom_activity_logger.cc133
-rw-r--r--chromium/extensions/renderer/dom_activity_logger.h91
-rw-r--r--chromium/extensions/renderer/event_bindings.cc367
-rw-r--r--chromium/extensions/renderer/event_bindings.h89
-rw-r--r--chromium/extensions/renderer/event_unittest.cc259
-rw-r--r--chromium/extensions/renderer/extension_frame_helper.cc277
-rw-r--r--chromium/extensions/renderer/extension_frame_helper.h148
-rw-r--r--chromium/extensions/renderer/extension_groups.h21
-rw-r--r--chromium/extensions/renderer/extension_helper.cc76
-rw-r--r--chromium/extensions/renderer/extension_helper.h37
-rw-r--r--chromium/extensions/renderer/extension_injection_host.cc88
-rw-r--r--chromium/extensions/renderer/extension_injection_host.h45
-rw-r--r--chromium/extensions/renderer/extensions_render_frame_observer.cc100
-rw-r--r--chromium/extensions/renderer/extensions_render_frame_observer.h38
-rw-r--r--chromium/extensions/renderer/extensions_renderer_client.cc26
-rw-r--r--chromium/extensions/renderer/extensions_renderer_client.h39
-rw-r--r--chromium/extensions/renderer/file_system_natives.cc124
-rw-r--r--chromium/extensions/renderer/file_system_natives.h31
-rw-r--r--chromium/extensions/renderer/gc_callback.cc58
-rw-r--r--chromium/extensions/renderer/gc_callback.h56
-rw-r--r--chromium/extensions/renderer/gc_callback_unittest.cc161
-rw-r--r--chromium/extensions/renderer/guest_view/OWNERS5
-rw-r--r--chromium/extensions/renderer/guest_view/extensions_guest_view_container.cc64
-rw-r--r--chromium/extensions/renderer/guest_view/extensions_guest_view_container.h50
-rw-r--r--chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.cc26
-rw-r--r--chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h23
-rw-r--r--chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc438
-rw-r--r--chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.h98
-rw-r--r--chromium/extensions/renderer/guest_view/mime_handler_view/OWNERS1
-rw-r--r--chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.cc340
-rw-r--r--chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h132
-rw-r--r--chromium/extensions/renderer/i18n_custom_bindings.cc245
-rw-r--r--chromium/extensions/renderer/i18n_custom_bindings.h26
-rw-r--r--chromium/extensions/renderer/id_generator_custom_bindings.cc31
-rw-r--r--chromium/extensions/renderer/id_generator_custom_bindings.h25
-rw-r--r--chromium/extensions/renderer/injection_host.cc12
-rw-r--r--chromium/extensions/renderer/injection_host.h47
-rw-r--r--chromium/extensions/renderer/json_schema_unittest.cc111
-rw-r--r--chromium/extensions/renderer/lazy_background_page_native_handler.cc46
-rw-r--r--chromium/extensions/renderer/lazy_background_page_native_handler.h23
-rw-r--r--chromium/extensions/renderer/logging_native_handler.cc78
-rw-r--r--chromium/extensions/renderer/logging_native_handler.h53
-rw-r--r--chromium/extensions/renderer/messaging_bindings.cc492
-rw-r--r--chromium/extensions/renderer/messaging_bindings.h73
-rw-r--r--chromium/extensions/renderer/messaging_utils_unittest.cc196
-rw-r--r--chromium/extensions/renderer/module_system.cc757
-rw-r--r--chromium/extensions/renderer/module_system.h253
-rw-r--r--chromium/extensions/renderer/module_system_test.cc261
-rw-r--r--chromium/extensions/renderer/module_system_test.h111
-rw-r--r--chromium/extensions/renderer/module_system_unittest.cc518
-rw-r--r--chromium/extensions/renderer/mojo/keep_alive_client_unittest.cc100
-rw-r--r--chromium/extensions/renderer/mojo/stash_client_unittest.cc55
-rw-r--r--chromium/extensions/renderer/native_handler.cc22
-rw-r--r--chromium/extensions/renderer/native_handler.h49
-rw-r--r--chromium/extensions/renderer/object_backed_native_handler.cc222
-rw-r--r--chromium/extensions/renderer/object_backed_native_handler.h123
-rw-r--r--chromium/extensions/renderer/print_native_handler.cc32
-rw-r--r--chromium/extensions/renderer/print_native_handler.h21
-rw-r--r--chromium/extensions/renderer/process_info_native_handler.cc98
-rw-r--r--chromium/extensions/renderer/process_info_native_handler.h43
-rw-r--r--chromium/extensions/renderer/programmatic_script_injector.cc185
-rw-r--r--chromium/extensions/renderer/programmatic_script_injector.h84
-rw-r--r--chromium/extensions/renderer/render_frame_observer_natives.cc107
-rw-r--r--chromium/extensions/renderer/render_frame_observer_natives.h38
-rw-r--r--chromium/extensions/renderer/renderer_extension_registry.cc105
-rw-r--r--chromium/extensions/renderer/renderer_extension_registry.h62
-rw-r--r--chromium/extensions/renderer/request_sender.cc143
-rw-r--r--chromium/extensions/renderer/request_sender.h107
-rw-r--r--chromium/extensions/renderer/resource_bundle_source_map.cc55
-rw-r--r--chromium/extensions/renderer/resource_bundle_source_map.h45
-rw-r--r--chromium/extensions/renderer/resources/OWNERS4
-rw-r--r--chromium/extensions/renderer/resources/app_runtime_custom_bindings.js83
-rw-r--r--chromium/extensions/renderer/resources/app_window_custom_bindings.js410
-rw-r--r--chromium/extensions/renderer/resources/async_waiter.js93
-rw-r--r--chromium/extensions/renderer/resources/binding.js574
-rw-r--r--chromium/extensions/renderer/resources/browser_test_environment_specific_bindings.js15
-rw-r--r--chromium/extensions/renderer/resources/context_menus_custom_bindings.js26
-rw-r--r--chromium/extensions/renderer/resources/context_menus_handlers.js141
-rw-r--r--chromium/extensions/renderer/resources/data_receiver.js336
-rw-r--r--chromium/extensions/renderer/resources/data_sender.js335
-rw-r--r--chromium/extensions/renderer/resources/declarative_webrequest_custom_bindings.js96
-rw-r--r--chromium/extensions/renderer/resources/display_source_custom_bindings.js69
-rw-r--r--chromium/extensions/renderer/resources/entry_id_manager.js52
-rw-r--r--chromium/extensions/renderer/resources/event.js520
-rw-r--r--chromium/extensions/renderer/resources/extension.css352
-rw-r--r--chromium/extensions/renderer/resources/extension_custom_bindings.js111
-rw-r--r--chromium/extensions/renderer/resources/extension_fonts.css12
-rw-r--r--chromium/extensions/renderer/resources/extensions_renderer_resources.grd111
-rw-r--r--chromium/extensions/renderer/resources/greasemonkey_api.js82
-rw-r--r--chromium/extensions/renderer/resources/guest_view/OWNERS4
-rw-r--r--chromium/extensions/renderer/resources/guest_view/app_view/app_view.js80
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_options/extension_options.js52
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_attributes.js43
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_constants.js14
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_events.js40
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view.js139
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_api_methods.js45
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_attributes.js55
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_constants.js14
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_events.js32
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_internal.js7
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view.js355
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_attributes.js142
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_container.js311
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_deny.js58
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_events.js178
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_iframe.js108
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_iframe_container.js30
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view.js235
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js296
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_api_methods.js204
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_attributes.js277
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_constants.js40
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_events.js301
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js24
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_internal.js7
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js55
-rw-r--r--chromium/extensions/renderer/resources/i18n_custom_bindings.js49
-rw-r--r--chromium/extensions/renderer/resources/image_util.js82
-rw-r--r--chromium/extensions/renderer/resources/json_schema.js525
-rw-r--r--chromium/extensions/renderer/resources/keep_alive.js41
-rw-r--r--chromium/extensions/renderer/resources/last_error.js143
-rw-r--r--chromium/extensions/renderer/resources/media_router_bindings.js777
-rw-r--r--chromium/extensions/renderer/resources/messaging.js446
-rw-r--r--chromium/extensions/renderer/resources/messaging_utils.js53
-rw-r--r--chromium/extensions/renderer/resources/mime_handler_private_custom_bindings.js75
-rw-r--r--chromium/extensions/renderer/resources/mojo_private_custom_bindings.js23
-rw-r--r--chromium/extensions/renderer/resources/permissions_custom_bindings.js92
-rw-r--r--chromium/extensions/renderer/resources/platform_app.css35
-rw-r--r--chromium/extensions/renderer/resources/platform_app.js232
-rw-r--r--chromium/extensions/renderer/resources/printer_provider_custom_bindings.js123
-rw-r--r--chromium/extensions/renderer/resources/runtime_custom_bindings.js206
-rw-r--r--chromium/extensions/renderer/resources/schema_utils.js159
-rw-r--r--chromium/extensions/renderer/resources/send_request.js151
-rw-r--r--chromium/extensions/renderer/resources/serial_custom_bindings.js117
-rw-r--r--chromium/extensions/renderer/resources/serial_service.js554
-rw-r--r--chromium/extensions/renderer/resources/service_worker_bindings.js67
-rw-r--r--chromium/extensions/renderer/resources/set_icon.js112
-rw-r--r--chromium/extensions/renderer/resources/stash_client.js168
-rw-r--r--chromium/extensions/renderer/resources/storage_area.js40
-rw-r--r--chromium/extensions/renderer/resources/test_custom_bindings.js364
-rw-r--r--chromium/extensions/renderer/resources/uncaught_exception_handler.js110
-rw-r--r--chromium/extensions/renderer/resources/utils.js240
-rw-r--r--chromium/extensions/renderer/resources/web_request_custom_bindings.js23
-rw-r--r--chromium/extensions/renderer/resources/web_request_internal_custom_bindings.js196
-rw-r--r--chromium/extensions/renderer/resources/window_controls.js78
-rw-r--r--chromium/extensions/renderer/resources/window_controls_template.html52
-rw-r--r--chromium/extensions/renderer/runtime_custom_bindings.cc184
-rw-r--r--chromium/extensions/renderer/runtime_custom_bindings.h34
-rw-r--r--chromium/extensions/renderer/safe_builtins.cc259
-rw-r--r--chromium/extensions/renderer/safe_builtins.h47
-rw-r--r--chromium/extensions/renderer/safe_builtins_unittest.cc66
-rw-r--r--chromium/extensions/renderer/scoped_web_frame.cc24
-rw-r--r--chromium/extensions/renderer/scoped_web_frame.h34
-rw-r--r--chromium/extensions/renderer/script_context.cc489
-rw-r--r--chromium/extensions/renderer/script_context.h259
-rw-r--r--chromium/extensions/renderer/script_context_browsertest.cc91
-rw-r--r--chromium/extensions/renderer/script_context_set.cc223
-rw-r--r--chromium/extensions/renderer/script_context_set.h145
-rw-r--r--chromium/extensions/renderer/script_context_set_unittest.cc62
-rw-r--r--chromium/extensions/renderer/script_context_unittest.cc24
-rw-r--r--chromium/extensions/renderer/script_injection.cc319
-rw-r--r--chromium/extensions/renderer/script_injection.h150
-rw-r--r--chromium/extensions/renderer/script_injection_callback.cc23
-rw-r--r--chromium/extensions/renderer/script_injection_callback.h42
-rw-r--r--chromium/extensions/renderer/script_injection_manager.cc514
-rw-r--r--chromium/extensions/renderer/script_injection_manager.h126
-rw-r--r--chromium/extensions/renderer/script_injector.h98
-rw-r--r--chromium/extensions/renderer/scripts_run_info.cc77
-rw-r--r--chromium/extensions/renderer/scripts_run_info.h64
-rw-r--r--chromium/extensions/renderer/send_request_natives.cc79
-rw-r--r--chromium/extensions/renderer/send_request_natives.h37
-rw-r--r--chromium/extensions/renderer/set_icon_natives.cc155
-rw-r--r--chromium/extensions/renderer/set_icon_natives.h33
-rw-r--r--chromium/extensions/renderer/static_v8_external_one_byte_string_resource.cc26
-rw-r--r--chromium/extensions/renderer/static_v8_external_one_byte_string_resource.h35
-rw-r--r--chromium/extensions/renderer/test_extensions_renderer_client.cc22
-rw-r--r--chromium/extensions/renderer/test_extensions_renderer_client.h28
-rw-r--r--chromium/extensions/renderer/test_features_native_handler.cc32
-rw-r--r--chromium/extensions/renderer/test_features_native_handler.h22
-rw-r--r--chromium/extensions/renderer/test_native_handler.cc24
-rw-r--r--chromium/extensions/renderer/test_native_handler.h29
-rw-r--r--chromium/extensions/renderer/user_gestures_native_handler.cc55
-rw-r--r--chromium/extensions/renderer/user_gestures_native_handler.h24
-rw-r--r--chromium/extensions/renderer/user_script_injector.cc268
-rw-r--r--chromium/extensions/renderer/user_script_injector.h86
-rw-r--r--chromium/extensions/renderer/user_script_set.cc235
-rw-r--r--chromium/extensions/renderer/user_script_set.h100
-rw-r--r--chromium/extensions/renderer/user_script_set_manager.cc159
-rw-r--r--chromium/extensions/renderer/user_script_set_manager.h113
-rw-r--r--chromium/extensions/renderer/utils_native_handler.cc29
-rw-r--r--chromium/extensions/renderer/utils_native_handler.h30
-rw-r--r--chromium/extensions/renderer/utils_unittest.cc77
-rw-r--r--chromium/extensions/renderer/v8_context_native_handler.cc64
-rw-r--r--chromium/extensions/renderer/v8_context_native_handler.h29
-rw-r--r--chromium/extensions/renderer/v8_helpers.h166
-rw-r--r--chromium/extensions/renderer/v8_schema_registry.cc142
-rw-r--r--chromium/extensions/renderer/v8_schema_registry.h55
-rw-r--r--chromium/extensions/renderer/wake_event_page.cc200
-rw-r--r--chromium/extensions/renderer/wake_event_page.h116
-rw-r--r--chromium/extensions/renderer/web_ui_injection_host.cc34
-rw-r--r--chromium/extensions/renderer/web_ui_injection_host.h33
-rw-r--r--chromium/extensions/renderer/worker_script_context_set.cc82
-rw-r--r--chromium/extensions/renderer/worker_script_context_set.h46
266 files changed, 35649 insertions, 0 deletions
diff --git a/chromium/extensions/renderer/BUILD.gn b/chromium/extensions/renderer/BUILD.gn
new file mode 100644
index 00000000000..1b8f7d0efbe
--- /dev/null
+++ b/chromium/extensions/renderer/BUILD.gn
@@ -0,0 +1,44 @@
+# Copyright 2014 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.
+
+import("//build/config/features.gni")
+import("//extensions/extensions.gni")
+
+assert(enable_extensions)
+
+# GYP version: extensions/extensions.gyp:extensions_renderer
+source_set("renderer") {
+ sources = rebase_path(extensions_gypi_values.extensions_renderer_sources,
+ ".",
+ "//extensions")
+
+ configs += [
+ "//build/config:precompiled_headers",
+
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ "//build/config/compiler:no_size_t_to_int_warning",
+ ]
+
+ deps = [
+ "//chrome:resources",
+ "//components/guest_view/renderer",
+ "//content:resources",
+ "//extensions:extensions_resources",
+ "//gin",
+ "//mojo/edk/js",
+ "//mojo/public/js",
+ "//skia",
+ "//third_party/WebKit/public:blink",
+ ]
+
+ if (enable_wifi_display) {
+ wifi_display_sources = rebase_path(
+ extensions_gypi_values.extensions_renderer_sources_wifi_display,
+ ".",
+ "//extensions")
+ sources += wifi_display_sources
+
+ deps += [ "//third_party/wds:libwds" ]
+ }
+}
diff --git a/chromium/extensions/renderer/DEPS b/chromium/extensions/renderer/DEPS
new file mode 100644
index 00000000000..6d1446295a6
--- /dev/null
+++ b/chromium/extensions/renderer/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+ "+components/translate/core/language_detection",
+ "+content/public/child",
+ "+content/public/renderer",
+
+ "+gin",
+ "+mojo/edk/js",
+
+ "+third_party/skia/include/core",
+ "+third_party/cld_2/src/public/compact_lang_det.h",
+ "+third_party/cld_2/src/public/encodings.h",
+
+ "+third_party/WebKit/public/platform",
+ "+third_party/WebKit/public/web",
+
+ "+third_party/wds/src/libwds/public",
+
+ "+tools/json_schema_compiler",
+
+ "-v8",
+ "+v8/include",
+
+ "+storage/common/fileapi",
+]
+
diff --git a/chromium/extensions/renderer/OWNERS b/chromium/extensions/renderer/OWNERS
new file mode 100644
index 00000000000..899a192f3bf
--- /dev/null
+++ b/chromium/extensions/renderer/OWNERS
@@ -0,0 +1,2 @@
+# For V8 related changes, please consider also adding
+jochen@chromium.org
diff --git a/chromium/extensions/renderer/activity_log_converter_strategy.cc b/chromium/extensions/renderer/activity_log_converter_strategy.cc
new file mode 100644
index 00000000000..f76f9558e84
--- /dev/null
+++ b/chromium/extensions/renderer/activity_log_converter_strategy.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/activity_log_converter_strategy.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+namespace {
+
+// Summarize a V8 value. This performs a shallow conversion in all cases, and
+// returns only a string with a description of the value (e.g.,
+// "[HTMLElement]").
+scoped_ptr<base::Value> SummarizeV8Value(v8::Isolate* isolate,
+ v8::Local<v8::Object> object) {
+ v8::TryCatch try_catch(isolate);
+ v8::Isolate::DisallowJavascriptExecutionScope scope(
+ isolate, v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
+ v8::Local<v8::String> name = v8::String::NewFromUtf8(isolate, "[");
+ if (object->IsFunction()) {
+ name =
+ v8::String::Concat(name, v8::String::NewFromUtf8(isolate, "Function"));
+ v8::Local<v8::Value> fname =
+ v8::Local<v8::Function>::Cast(object)->GetName();
+ if (fname->IsString() && v8::Local<v8::String>::Cast(fname)->Length()) {
+ name = v8::String::Concat(name, v8::String::NewFromUtf8(isolate, " "));
+ name = v8::String::Concat(name, v8::Local<v8::String>::Cast(fname));
+ name = v8::String::Concat(name, v8::String::NewFromUtf8(isolate, "()"));
+ }
+ } else {
+ name = v8::String::Concat(name, object->GetConstructorName());
+ }
+ name = v8::String::Concat(name, v8::String::NewFromUtf8(isolate, "]"));
+
+ if (try_catch.HasCaught()) {
+ return scoped_ptr<base::Value>(
+ new base::StringValue("[JS Execution Exception]"));
+ }
+
+ return scoped_ptr<base::Value>(
+ new base::StringValue(std::string(*v8::String::Utf8Value(name))));
+}
+
+} // namespace
+
+ActivityLogConverterStrategy::ActivityLogConverterStrategy() {}
+
+ActivityLogConverterStrategy::~ActivityLogConverterStrategy() {}
+
+bool ActivityLogConverterStrategy::FromV8Object(
+ v8::Local<v8::Object> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const {
+ return FromV8Internal(value, out, isolate, callback);
+}
+
+bool ActivityLogConverterStrategy::FromV8Array(
+ v8::Local<v8::Array> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const {
+ return FromV8Internal(value, out, isolate, callback);
+}
+
+bool ActivityLogConverterStrategy::FromV8Internal(
+ v8::Local<v8::Object> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const {
+ scoped_ptr<base::Value> parsed_value;
+ parsed_value = SummarizeV8Value(isolate, value);
+ *out = parsed_value.release();
+
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/activity_log_converter_strategy.h b/chromium/extensions/renderer/activity_log_converter_strategy.h
new file mode 100644
index 00000000000..fd7a1d2babf
--- /dev/null
+++ b/chromium/extensions/renderer/activity_log_converter_strategy.h
@@ -0,0 +1,50 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_ACTIVITY_LOG_CONVERTER_STRATEGY_H_
+#define EXTENSIONS_RENDERER_ACTIVITY_LOG_CONVERTER_STRATEGY_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "content/public/child/v8_value_converter.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// This class is used by activity logger and extends behavior of
+// V8ValueConverter. It overwrites conversion of V8 arrays and objects. When
+// converting arrays and objects, we must not invoke any JS code which may
+// result in triggering activity logger. In such case, the log entries will be
+// generated due to V8 object conversion rather than extension activity.
+class ActivityLogConverterStrategy
+ : public content::V8ValueConverter::Strategy {
+ public:
+ typedef content::V8ValueConverter::Strategy::FromV8ValueCallback
+ FromV8ValueCallback;
+
+ ActivityLogConverterStrategy();
+ ~ActivityLogConverterStrategy() override;
+
+ // content::V8ValueConverter::Strategy implementation.
+ bool FromV8Object(v8::Local<v8::Object> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const override;
+ bool FromV8Array(v8::Local<v8::Array> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const override;
+
+ private:
+ bool FromV8Internal(v8::Local<v8::Object> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const;
+
+ DISALLOW_COPY_AND_ASSIGN(ActivityLogConverterStrategy);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_ACTIVITY_LOG_CONVERTER_STRATEGY_H_
diff --git a/chromium/extensions/renderer/activity_log_converter_strategy_unittest.cc b/chromium/extensions/renderer/activity_log_converter_strategy_unittest.cc
new file mode 100644
index 00000000000..fbb9597d661
--- /dev/null
+++ b/chromium/extensions/renderer/activity_log_converter_strategy_unittest.cc
@@ -0,0 +1,172 @@
+// Copyright 2014 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.
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/renderer/activity_log_converter_strategy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "v8/include/v8.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+class ActivityLogConverterStrategyTest : public testing::Test {
+ public:
+ ActivityLogConverterStrategyTest()
+ : isolate_(v8::Isolate::GetCurrent()),
+ handle_scope_(isolate_),
+ context_(isolate_, v8::Context::New(isolate_)),
+ context_scope_(context()) {}
+
+ protected:
+ void SetUp() override {
+ converter_.reset(V8ValueConverter::create());
+ strategy_.reset(new ActivityLogConverterStrategy());
+ converter_->SetFunctionAllowed(true);
+ converter_->SetStrategy(strategy_.get());
+ }
+
+ testing::AssertionResult VerifyNull(v8::Local<v8::Value> v8_value) {
+ scoped_ptr<base::Value> value(
+ converter_->FromV8Value(v8_value, context()));
+ if (value->IsType(base::Value::TYPE_NULL))
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure();
+ }
+
+ testing::AssertionResult VerifyBoolean(v8::Local<v8::Value> v8_value,
+ bool expected) {
+ bool out;
+ scoped_ptr<base::Value> value(
+ converter_->FromV8Value(v8_value, context()));
+ if (value->IsType(base::Value::TYPE_BOOLEAN)
+ && value->GetAsBoolean(&out)
+ && out == expected)
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure();
+ }
+
+ testing::AssertionResult VerifyInteger(v8::Local<v8::Value> v8_value,
+ int expected) {
+ int out;
+ scoped_ptr<base::Value> value(
+ converter_->FromV8Value(v8_value, context()));
+ if (value->IsType(base::Value::TYPE_INTEGER)
+ && value->GetAsInteger(&out)
+ && out == expected)
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure();
+ }
+
+ testing::AssertionResult VerifyDouble(v8::Local<v8::Value> v8_value,
+ double expected) {
+ double out;
+ scoped_ptr<base::Value> value(
+ converter_->FromV8Value(v8_value, context()));
+ if (value->IsType(base::Value::TYPE_DOUBLE)
+ && value->GetAsDouble(&out)
+ && out == expected)
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure();
+ }
+
+ testing::AssertionResult VerifyString(v8::Local<v8::Value> v8_value,
+ const std::string& expected) {
+ std::string out;
+ scoped_ptr<base::Value> value(
+ converter_->FromV8Value(v8_value, context()));
+ if (value->IsType(base::Value::TYPE_STRING)
+ && value->GetAsString(&out)
+ && out == expected)
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure();
+ }
+
+ v8::Local<v8::Context> context() const {
+ return v8::Local<v8::Context>::New(isolate_, context_);
+ }
+
+ v8::Isolate* isolate_;
+ v8::HandleScope handle_scope_;
+ v8::Global<v8::Context> context_;
+ v8::Context::Scope context_scope_;
+ scoped_ptr<V8ValueConverter> converter_;
+ scoped_ptr<ActivityLogConverterStrategy> strategy_;
+};
+
+TEST_F(ActivityLogConverterStrategyTest, ConversionTest) {
+ const char* source = "(function() {"
+ "function foo() {}"
+ "return {"
+ "null: null,"
+ "true: true,"
+ "false: false,"
+ "positive_int: 42,"
+ "negative_int: -42,"
+ "zero: 0,"
+ "double: 88.8,"
+ "big_integral_double: 9007199254740992.0," // 2.0^53
+ "string: \"foobar\","
+ "empty_string: \"\","
+ "dictionary: {"
+ "foo: \"bar\","
+ "hot: \"dog\","
+ "},"
+ "empty_dictionary: {},"
+ "list: [ \"monkey\", \"balls\" ],"
+ "empty_list: [],"
+ "function: (0, function() {})," // ensure function is anonymous
+ "named_function: foo"
+ "};"
+ "})();";
+
+ v8::MicrotasksScope microtasks(
+ isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);
+ v8::Local<v8::Script> script(
+ v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
+ v8::Local<v8::Object> v8_object = script->Run().As<v8::Object>();
+
+ EXPECT_TRUE(VerifyString(v8_object, "[Object]"));
+ EXPECT_TRUE(
+ VerifyNull(v8_object->Get(v8::String::NewFromUtf8(isolate_, "null"))));
+ EXPECT_TRUE(VerifyBoolean(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "true")), true));
+ EXPECT_TRUE(VerifyBoolean(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "false")), false));
+ EXPECT_TRUE(VerifyInteger(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "positive_int")), 42));
+ EXPECT_TRUE(VerifyInteger(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "negative_int")), -42));
+ EXPECT_TRUE(VerifyInteger(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "zero")), 0));
+ EXPECT_TRUE(VerifyDouble(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "double")), 88.8));
+ EXPECT_TRUE(VerifyDouble(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "big_integral_double")),
+ 9007199254740992.0));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "string")), "foobar"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "empty_string")), ""));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "dictionary")),
+ "[Object]"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "empty_dictionary")),
+ "[Object]"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "list")), "[Array]"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "empty_list")),
+ "[Array]"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "function")),
+ "[Function]"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "named_function")),
+ "[Function foo()]"));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/automation/automation_api_helper.cc b/chromium/extensions/renderer/api/automation/automation_api_helper.cc
new file mode 100644
index 00000000000..ab10682f6db
--- /dev/null
+++ b/chromium/extensions/renderer/api/automation/automation_api_helper.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/api/automation/automation_api_helper.h"
+
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/extension_messages.h"
+#include "third_party/WebKit/public/web/WebAXObject.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebElement.h"
+#include "third_party/WebKit/public/web/WebExceptionCode.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebNode.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+namespace extensions {
+
+AutomationApiHelper::AutomationApiHelper(content::RenderView* render_view)
+ : content::RenderViewObserver(render_view) {
+}
+
+AutomationApiHelper::~AutomationApiHelper() {
+}
+
+bool AutomationApiHelper::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(AutomationApiHelper, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_AutomationQuerySelector, OnQuerySelector)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void AutomationApiHelper::OnQuerySelector(int request_id,
+ int acc_obj_id,
+ const base::string16& selector) {
+ ExtensionHostMsg_AutomationQuerySelector_Error error;
+ if (!render_view() || !render_view()->GetWebView() ||
+ !render_view()->GetWebView()->mainFrame()) {
+ error.value = ExtensionHostMsg_AutomationQuerySelector_Error::kNoMainFrame;
+ Send(new ExtensionHostMsg_AutomationQuerySelector_Result(
+ routing_id(), request_id, error, 0));
+ return;
+ }
+ blink::WebDocument document =
+ render_view()->GetWebView()->mainFrame()->document();
+ if (document.isNull()) {
+ error.value =
+ ExtensionHostMsg_AutomationQuerySelector_Error::kNoDocument;
+ Send(new ExtensionHostMsg_AutomationQuerySelector_Result(
+ routing_id(), request_id, error, 0));
+ return;
+ }
+ blink::WebNode start_node = document;
+ if (acc_obj_id > 0) {
+ blink::WebAXObject start_acc_obj =
+ document.accessibilityObjectFromID(acc_obj_id);
+ if (start_acc_obj.isNull()) {
+ error.value =
+ ExtensionHostMsg_AutomationQuerySelector_Error::kNodeDestroyed;
+ Send(new ExtensionHostMsg_AutomationQuerySelector_Result(
+ routing_id(), request_id, error, 0));
+ return;
+ }
+
+ start_node = start_acc_obj.node();
+ while (start_node.isNull()) {
+ start_acc_obj = start_acc_obj.parentObject();
+ start_node = start_acc_obj.node();
+ }
+ }
+ blink::WebString web_selector(selector);
+ blink::WebExceptionCode ec = 0;
+ blink::WebElement result_element = start_node.querySelector(web_selector, ec);
+ int result_acc_obj_id = 0;
+ if (!ec && !result_element.isNull()) {
+ blink::WebAXObject result_acc_obj = result_element.accessibilityObject();
+ if (!result_acc_obj.isDetached()) {
+ while (result_acc_obj.accessibilityIsIgnored())
+ result_acc_obj = result_acc_obj.parentObject();
+
+ result_acc_obj_id = result_acc_obj.axID();
+ }
+ }
+ Send(new ExtensionHostMsg_AutomationQuerySelector_Result(
+ routing_id(), request_id, error, result_acc_obj_id));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/automation/automation_api_helper.h b/chromium/extensions/renderer/api/automation/automation_api_helper.h
new file mode 100644
index 00000000000..32aa2ef0dff
--- /dev/null
+++ b/chromium/extensions/renderer/api/automation/automation_api_helper.h
@@ -0,0 +1,34 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_API_AUTOMATION_AUTOMATION_API_HELPER_H_
+#define EXTENSIONS_RENDERER_API_AUTOMATION_AUTOMATION_API_HELPER_H_
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "content/public/renderer/render_view_observer.h"
+
+namespace extensions {
+
+// Renderer-side implementation for chrome.automation API (for the few pieces
+// which aren't built in to the existing accessibility system).
+class AutomationApiHelper : public content::RenderViewObserver {
+ public:
+ explicit AutomationApiHelper(content::RenderView* render_view);
+ ~AutomationApiHelper() override;
+
+ private:
+ // RenderViewObserver implementation.
+ bool OnMessageReceived(const IPC::Message& message) override;
+
+ void OnQuerySelector(int acc_obj_id,
+ int request_id,
+ const base::string16& selector);
+
+ DISALLOW_COPY_AND_ASSIGN(AutomationApiHelper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_AUTOMATION_AUTOMATION_API_HELPER_H_
diff --git a/chromium/extensions/renderer/api/display_source/OWNERS b/chromium/extensions/renderer/api/display_source/OWNERS
new file mode 100644
index 00000000000..f7e4f6f8751
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/OWNERS
@@ -0,0 +1,2 @@
+alexander.shalamov@intel.com
+mikhail.pozdnyakov@intel.com
diff --git a/chromium/extensions/renderer/api/display_source/display_source_session.cc b/chromium/extensions/renderer/api/display_source/display_source_session.cc
new file mode 100644
index 00000000000..20b14420dd9
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/display_source_session.cc
@@ -0,0 +1,43 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/api/display_source/display_source_session.h"
+
+#if defined(ENABLE_WIFI_DISPLAY)
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_session.h"
+#endif
+
+namespace extensions {
+
+DisplaySourceSessionParams::DisplaySourceSessionParams()
+ : auth_method(api::display_source::AUTHENTICATION_METHOD_NONE) {
+}
+
+DisplaySourceSessionParams::~DisplaySourceSessionParams() = default;
+
+DisplaySourceSession::DisplaySourceSession()
+ : state_(Idle) {
+}
+
+DisplaySourceSession::~DisplaySourceSession() = default;
+
+void DisplaySourceSession::SetNotificationCallbacks(
+ const base::Closure& terminated_callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(terminated_callback_.is_null());
+ DCHECK(error_callback_.is_null());
+
+ terminated_callback_ = terminated_callback;
+ error_callback_ = error_callback;
+}
+
+scoped_ptr<DisplaySourceSession> DisplaySourceSessionFactory::CreateSession(
+ const DisplaySourceSessionParams& params) {
+#if defined(ENABLE_WIFI_DISPLAY)
+ return scoped_ptr<DisplaySourceSession>(new WiFiDisplaySession(params));
+#endif
+ return nullptr;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/display_source_session.h b/chromium/extensions/renderer/api/display_source/display_source_session.h
new file mode 100644
index 00000000000..922126d994b
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/display_source_session.h
@@ -0,0 +1,116 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_SESSION_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_SESSION_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/api/display_source.h"
+#include "third_party/WebKit/public/web/WebDOMMediaStreamTrack.h"
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+
+using DisplaySourceAuthInfo = api::display_source::AuthenticationInfo;
+using DisplaySourceAuthMethod = api::display_source::AuthenticationMethod;
+using DisplaySourceErrorType = api::display_source::ErrorType;
+
+// This class represents a generic display source session interface.
+class DisplaySourceSession {
+ public:
+ using CompletionCallback =
+ base::Callback<void(bool success, const std::string& error_description)>;
+ using ErrorCallback =
+ base::Callback<void(DisplaySourceErrorType error_type,
+ const std::string& error_description)>;
+
+ // State flow is ether:
+ // 'Idle' -> 'Establishing' -> 'Established' -> 'Terminating' -> 'Idle'
+ // (terminated by Terminate() call)
+ // or
+ // 'Idle' -> 'Establishing' -> 'Established' -> 'Idle'
+ // (terminated from sink device or due to an error)
+ enum State {
+ Idle,
+ Establishing,
+ Established,
+ Terminating
+ };
+
+ virtual ~DisplaySourceSession();
+
+ // Starts the session.
+ // The session state should be set to 'Establishing' immediately after this
+ // method is called.
+ // |callback| : Called with 'success' flag set to 'true' if the session is
+ // successfully started (state should be set to 'Established')
+ //
+ // Called with 'success' flag set to 'false' if the session
+ // has failed to start (state should be set back to 'Idle').
+ // The 'error_description' argument contains description of
+ // an error that had caused the call falure.
+ virtual void Start(const CompletionCallback& callback) = 0;
+
+ // Terminates the session.
+ // The session state should be set to 'Terminating' immediately after this
+ // method is called.
+ // |callback| : Called with 'success' flag set to 'true' if the session is
+ // successfully terminated (state should be set to 'Idle')
+ //
+ // Called with 'success' flag set to 'false' if the session
+ // could not terminate (state should not be modified).
+ // The 'error_description' argument contains description of
+ // an error that had caused the call falure.
+ virtual void Terminate(const CompletionCallback& callback) = 0;
+
+ State state() const { return state_; }
+
+ // Sets the callbacks invoked to inform about the session's state changes.
+ // It is required to set the callbacks before the session is started.
+ // |terminated_callback| : Called when session was terminated (state
+ // should be set to 'Idle')
+ // |error_callback| : Called if a fatal error has occured and the session
+ // will be terminated soon for emergency reasons.
+ void SetNotificationCallbacks(const base::Closure& terminated_callback,
+ const ErrorCallback& error_callback);
+
+ protected:
+ DisplaySourceSession();
+
+ State state_;
+ base::Closure terminated_callback_;
+ ErrorCallback error_callback_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceSession);
+};
+
+struct DisplaySourceSessionParams {
+ DisplaySourceSessionParams();
+ ~DisplaySourceSessionParams();
+
+ int sink_id;
+ blink::WebMediaStreamTrack video_track;
+ blink::WebMediaStreamTrack audio_track;
+ DisplaySourceAuthMethod auth_method;
+ std::string auth_data;
+ content::RenderFrame* render_frame;
+};
+
+class DisplaySourceSessionFactory {
+ public:
+ static scoped_ptr<DisplaySourceSession> CreateSession(
+ const DisplaySourceSessionParams& params);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceSessionFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_SESSION_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.cc
new file mode 100644
index 00000000000..d17ca85056b
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.cc
@@ -0,0 +1,166 @@
+// Copyright 2016 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.
+
+#include "base/logging.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h"
+
+#include <cstring>
+
+namespace extensions {
+
+WiFiDisplayElementaryStreamDescriptor::WiFiDisplayElementaryStreamDescriptor(
+ const WiFiDisplayElementaryStreamDescriptor& other) {
+ if (!other.empty()) {
+ data_.reset(new uint8_t[other.size()]);
+ std::memcpy(data(), other.data(), other.size());
+ }
+}
+
+WiFiDisplayElementaryStreamDescriptor::WiFiDisplayElementaryStreamDescriptor(
+ WiFiDisplayElementaryStreamDescriptor&&) = default;
+
+WiFiDisplayElementaryStreamDescriptor::WiFiDisplayElementaryStreamDescriptor(
+ DescriptorTag tag,
+ uint8_t length)
+ : data_(new uint8_t[kHeaderSize + length]) {
+ uint8_t* p = data();
+ *p++ = tag;
+ *p++ = length;
+ DCHECK_EQ(private_data(), p);
+}
+
+WiFiDisplayElementaryStreamDescriptor::
+ ~WiFiDisplayElementaryStreamDescriptor() {}
+
+WiFiDisplayElementaryStreamDescriptor& WiFiDisplayElementaryStreamDescriptor::
+operator=(WiFiDisplayElementaryStreamDescriptor&&) = default;
+
+const uint8_t* WiFiDisplayElementaryStreamDescriptor::data() const {
+ return data_.get();
+}
+
+uint8_t* WiFiDisplayElementaryStreamDescriptor::data() {
+ return data_.get();
+}
+
+size_t WiFiDisplayElementaryStreamDescriptor::size() const {
+ if (empty())
+ return 0u;
+ return kHeaderSize + length();
+}
+
+WiFiDisplayElementaryStreamDescriptor
+WiFiDisplayElementaryStreamDescriptor::AVCTimingAndHRD::Create() {
+ WiFiDisplayElementaryStreamDescriptor descriptor(
+ DESCRIPTOR_TAG_AVC_TIMING_AND_HRD, 2u);
+ uint8_t* p = descriptor.private_data();
+ *p++ = (false << 7) | // hrd_management_valid_flag
+ (0x3Fu << 1) | // reserved (all six bits on)
+ (false << 0); // picture_and_timing_info_present
+ // No picture nor timing info bits.
+ *p++ = (false << 7) | // fixed_frame_rate_flag
+ (false << 6) | // temporal_poc_flag
+ (false << 5) | // picture_to_display_conversion_flag
+ (0x1Fu << 0); // reserved (all five bits on)
+ DCHECK_EQ(descriptor.end(), p);
+ return descriptor;
+}
+
+WiFiDisplayElementaryStreamDescriptor
+WiFiDisplayElementaryStreamDescriptor::AVCVideo::Create(
+ uint8_t profile_idc,
+ bool constraint_set0_flag,
+ bool constraint_set1_flag,
+ bool constraint_set2_flag,
+ uint8_t avc_compatible_flags,
+ uint8_t level_idc,
+ bool avc_still_present) {
+ const bool avc_24_hour_picture_flag = false;
+ WiFiDisplayElementaryStreamDescriptor descriptor(DESCRIPTOR_TAG_AVC_VIDEO,
+ 4u);
+ uint8_t* p = descriptor.private_data();
+ *p++ = profile_idc;
+ *p++ = (constraint_set0_flag << 7) | (constraint_set1_flag << 6) |
+ (constraint_set2_flag << 5) | (avc_compatible_flags << 0);
+ *p++ = level_idc;
+ *p++ = (avc_still_present << 7) | (avc_24_hour_picture_flag << 6) |
+ (0x3Fu << 0); // Reserved (all 6 bits on)
+ DCHECK_EQ(descriptor.end(), p);
+ return descriptor;
+}
+
+namespace {
+struct LPCMAudioStreamByte0 {
+ enum : uint8_t {
+ kBitsPerSampleShift = 3u,
+ kBitsPerSampleMask = ((1u << 2) - 1u) << kBitsPerSampleShift,
+ kEmphasisFlagShift = 0u,
+ kEmphasisFlagMask = 1u << kEmphasisFlagShift,
+ kReservedOnBitsShift = 1u,
+ kReservedOnBitsMask = ((1u << 2) - 1u) << kReservedOnBitsShift,
+ kSamplingFrequencyShift = 5u,
+ kSamplingFrequencyMask = ((1u << 3) - 1u) << kSamplingFrequencyShift,
+ };
+};
+
+struct LPCMAudioStreamByte1 {
+ enum : uint8_t {
+ kNumberOfChannelsShift = 5u,
+ kNumberOfChannelsMask = ((1u << 3) - 1u) << kNumberOfChannelsShift,
+ kReservedOnBitsShift = 0u,
+ kReservedOnBitsMask = ((1u << 4) - 1u) << kReservedOnBitsShift,
+ // The bit not listed above having a shift 4u is a reserved off bit.
+ };
+};
+} // namespace
+
+WiFiDisplayElementaryStreamDescriptor
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::Create(
+ SamplingFrequency sampling_frequency,
+ BitsPerSample bits_per_sample,
+ bool emphasis_flag,
+ NumberOfChannels number_of_channels) {
+ WiFiDisplayElementaryStreamDescriptor descriptor(
+ DESCRIPTOR_TAG_LPCM_AUDIO_STREAM, 2u);
+ uint8_t* p = descriptor.private_data();
+ *p++ = (sampling_frequency << LPCMAudioStreamByte0::kSamplingFrequencyShift) |
+ (bits_per_sample << LPCMAudioStreamByte0::kBitsPerSampleShift) |
+ LPCMAudioStreamByte0::kReservedOnBitsMask |
+ (emphasis_flag << LPCMAudioStreamByte0::kEmphasisFlagShift);
+ *p++ = (number_of_channels << LPCMAudioStreamByte1::kNumberOfChannelsShift) |
+ LPCMAudioStreamByte1::kReservedOnBitsMask;
+ DCHECK_EQ(descriptor.end(), p);
+ return descriptor;
+}
+
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::BitsPerSample
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::bits_per_sample()
+ const {
+ return static_cast<BitsPerSample>(
+ (private_data()[0] & LPCMAudioStreamByte0::kBitsPerSampleMask) >>
+ LPCMAudioStreamByte0::kBitsPerSampleShift);
+}
+
+bool WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::emphasis_flag()
+ const {
+ return (private_data()[0] & LPCMAudioStreamByte0::kEmphasisFlagMask) != 0u;
+}
+
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::NumberOfChannels
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::number_of_channels()
+ const {
+ return static_cast<NumberOfChannels>(
+ (private_data()[1] & LPCMAudioStreamByte1::kNumberOfChannelsMask) >>
+ LPCMAudioStreamByte1::kNumberOfChannelsShift);
+}
+
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::SamplingFrequency
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::sampling_frequency()
+ const {
+ return static_cast<SamplingFrequency>(
+ (private_data()[0] & LPCMAudioStreamByte0::kSamplingFrequencyMask) >>
+ LPCMAudioStreamByte0::kSamplingFrequencyShift);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h
new file mode 100644
index 00000000000..c149af851f8
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h
@@ -0,0 +1,122 @@
+// Copyright 2016 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.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_DESCRIPTOR_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_DESCRIPTOR_H_
+
+#include <stdint.h>
+#include <type_traits>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace extensions {
+
+// WiFi Display elementary stream descriptors are used for passing descriptive
+// information about elementary streams to WiFiDisplayTransportStreamPacketizer
+// which then packetizes that information so that it can be passed to a remote
+// sink.
+class WiFiDisplayElementaryStreamDescriptor {
+ public:
+ enum { kHeaderSize = 2u };
+
+ enum DescriptorTag : uint8_t {
+ DESCRIPTOR_TAG_AVC_VIDEO = 0x28u,
+ DESCRIPTOR_TAG_AVC_TIMING_AND_HRD = 0x2A,
+ DESCRIPTOR_TAG_LPCM_AUDIO_STREAM = 0x83u,
+ };
+
+ // Make Google Test treat this class as a container.
+ using const_iterator = const uint8_t*;
+ using iterator = const uint8_t*;
+
+ WiFiDisplayElementaryStreamDescriptor(
+ const WiFiDisplayElementaryStreamDescriptor&);
+ WiFiDisplayElementaryStreamDescriptor(
+ WiFiDisplayElementaryStreamDescriptor&&);
+ ~WiFiDisplayElementaryStreamDescriptor();
+
+ WiFiDisplayElementaryStreamDescriptor& operator=(
+ WiFiDisplayElementaryStreamDescriptor&&);
+
+ const uint8_t* begin() const { return data(); }
+ const uint8_t* data() const;
+ bool empty() const { return !data_; }
+ const uint8_t* end() const { return data() + size(); }
+ size_t size() const;
+
+ DescriptorTag tag() const { return static_cast<DescriptorTag>(data()[0]); }
+ uint8_t length() const { return data()[1]; }
+
+ // AVC (Advanced Video Coding) timing and HRD (Hypothetical Reference
+ // Decoder) descriptor provides timing and HRD parameters for a video stream.
+ struct AVCTimingAndHRD {
+ static WiFiDisplayElementaryStreamDescriptor Create();
+ };
+
+ // AVC (Advanced Video Coding) video descriptor provides basic coding
+ // parameters for a video stream.
+ struct AVCVideo {
+ static WiFiDisplayElementaryStreamDescriptor Create(
+ uint8_t profile_idc,
+ bool constraint_set0_flag,
+ bool constraint_set1_flag,
+ bool constraint_set2_flag,
+ uint8_t avc_compatible_flags,
+ uint8_t level_idc,
+ bool avc_still_present);
+ };
+
+ class LPCMAudioStream;
+
+ protected:
+ WiFiDisplayElementaryStreamDescriptor(DescriptorTag tag, uint8_t length);
+
+ uint8_t* data();
+ const uint8_t* private_data() const { return data() + kHeaderSize; }
+ uint8_t* private_data() { return data() + kHeaderSize; }
+
+ private:
+ scoped_ptr<uint8_t[]> data_;
+};
+
+// LPCM (Linear pulse-code modulation) audio stream descriptor provides basic
+// coding parameters for a private WiFi Display LPCM audio stream.
+class WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream
+ : public WiFiDisplayElementaryStreamDescriptor {
+ public:
+ enum { kTag = DESCRIPTOR_TAG_LPCM_AUDIO_STREAM };
+ enum BitsPerSample : uint8_t { BITS_PER_SAMPLE_16 = 0u };
+ enum NumberOfChannels : uint8_t {
+ NUMBER_OF_CHANNELS_DUAL_MONO = 0u,
+ NUMBER_OF_CHANNELS_STEREO = 1u
+ };
+ enum SamplingFrequency : uint8_t {
+ SAMPLING_FREQUENCY_44_1K = 1u,
+ SAMPLING_FREQUENCY_48K = 2u
+ };
+
+ static WiFiDisplayElementaryStreamDescriptor Create(
+ SamplingFrequency sampling_frequency,
+ BitsPerSample bits_per_sample,
+ bool emphasis_flag,
+ NumberOfChannels number_of_channels);
+
+ BitsPerSample bits_per_sample() const;
+ bool emphasis_flag() const;
+ NumberOfChannels number_of_channels() const;
+ SamplingFrequency sampling_frequency() const;
+};
+
+// Subclasses of WiFiDisplayElementaryStreamDescriptor MUST NOT define new
+// member variables but only new non-virtual member functions which parse
+// the inherited data. This allows WiFiDisplayElementaryStreamDescriptor
+// pointers to be cast to subclass pointers.
+static_assert(
+ std::is_standard_layout<
+ WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream>::value,
+ "Forbidden memory layout for an elementary stream descriptor");
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_DESCRIPTOR_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor_unittest.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor_unittest.cc
new file mode 100644
index 00000000000..4e049db9ba7
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor_unittest.cc
@@ -0,0 +1,151 @@
+// Copyright 2016 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.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using LPCMAudioStreamDescriptor =
+ extensions::WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream;
+
+namespace extensions {
+
+namespace {
+
+// Copy constructors cannot always be tested by calling them directly as
+// a compiler is allowed to optimize copy constructor calls away in certain
+// cases (that is called return value optimization). Therefore, this helper
+// function is needed to really create a copy of an object.
+template <typename T>
+T Copy(const T& t) {
+ return t;
+}
+
+class Data : public std::vector<uint8_t> {
+ public:
+ template <size_t N>
+ explicit Data(const char (&str)[N]) {
+ EXPECT_EQ('\0', str[N - 1]);
+ insert(end(), str, str + N - 1);
+ }
+
+ bool operator==(const WiFiDisplayElementaryStreamDescriptor& rhs) const {
+ return size() == rhs.size() && std::equal(begin(), end(), rhs.begin());
+ }
+};
+
+TEST(WiFiDisplayElementaryStreamDescriptorTest, AVCTimingAndHRDDescriptor) {
+ using AVCTimingAndHRDDescriptor =
+ WiFiDisplayElementaryStreamDescriptor::AVCTimingAndHRD;
+ EXPECT_EQ(Data("\x2A\x02\x7E\x1F"), AVCTimingAndHRDDescriptor::Create());
+ EXPECT_EQ(Data("\x2A\x02\x7E\x1F"),
+ Copy(AVCTimingAndHRDDescriptor::Create()));
+}
+
+TEST(WiFiDisplayElementaryStreamDescriptorTest, AVCVideoDescriptor) {
+ using AVCVideoDescriptor = WiFiDisplayElementaryStreamDescriptor::AVCVideo;
+ EXPECT_EQ(
+ Data("\x28\x04\x00\x00\x00\x3F"),
+ AVCVideoDescriptor::Create(0x0u, false, false, false, 0x0u, 0x0u, false));
+ EXPECT_EQ(Data("\x28\x04\x00\x00\x00\x3F"),
+ Copy(AVCVideoDescriptor::Create(0x0u, false, false, false, 0x0u,
+ 0x0u, false)));
+ EXPECT_EQ(Data("\x28\x04\xFF\x00\x00\x3F"),
+ AVCVideoDescriptor::Create(0xFFu, false, false, false, 0x0u, 0x0u,
+ false));
+ EXPECT_EQ(
+ Data("\x28\x04\x00\x80\x00\x3F"),
+ AVCVideoDescriptor::Create(0x0u, true, false, false, 0x0u, 0x0u, false));
+ EXPECT_EQ(
+ Data("\x28\x04\x00\x40\x00\x3F"),
+ AVCVideoDescriptor::Create(0x0u, false, true, false, 0x0u, 0x0u, false));
+ EXPECT_EQ(
+ Data("\x28\x04\x00\x20\x00\x3F"),
+ AVCVideoDescriptor::Create(0x0u, false, false, true, 0x0u, 0x0u, false));
+ EXPECT_EQ(Data("\x28\x04\x00\x1F\x00\x3F"),
+ AVCVideoDescriptor::Create(0x0u, false, false, false, 0x1Fu, 0x0u,
+ false));
+ EXPECT_EQ(Data("\x28\x04\x00\x00\xFF\x3F"),
+ AVCVideoDescriptor::Create(0x0u, false, false, false, 0x0u, 0xFFu,
+ false));
+ EXPECT_EQ(
+ Data("\x28\x04\x00\x00\x00\xBF"),
+ AVCVideoDescriptor::Create(0x0u, false, false, false, 0x0u, 0x0u, true));
+}
+
+class LPCMAudioStreamDescriptorTest
+ : public testing::TestWithParam<
+ testing::tuple<LPCMAudioStreamDescriptor::SamplingFrequency,
+ LPCMAudioStreamDescriptor::BitsPerSample,
+ bool,
+ LPCMAudioStreamDescriptor::NumberOfChannels,
+ Data>> {
+ protected:
+ LPCMAudioStreamDescriptorTest()
+ : sampling_frequency_(testing::get<0>(GetParam())),
+ bits_per_sample_(testing::get<1>(GetParam())),
+ emphasis_flag_(testing::get<2>(GetParam())),
+ number_of_channels_(testing::get<3>(GetParam())),
+ expected_data_(testing::get<4>(GetParam())),
+ descriptor_(LPCMAudioStreamDescriptor::Create(sampling_frequency_,
+ bits_per_sample_,
+ emphasis_flag_,
+ number_of_channels_)) {}
+
+ const LPCMAudioStreamDescriptor::SamplingFrequency sampling_frequency_;
+ const LPCMAudioStreamDescriptor::BitsPerSample bits_per_sample_;
+ const bool emphasis_flag_;
+ const LPCMAudioStreamDescriptor::NumberOfChannels number_of_channels_;
+ const Data expected_data_;
+ const WiFiDisplayElementaryStreamDescriptor descriptor_;
+};
+
+TEST_P(LPCMAudioStreamDescriptorTest, Create) {
+ EXPECT_EQ(expected_data_, descriptor_);
+ EXPECT_EQ(expected_data_, Copy(descriptor_));
+}
+
+TEST_P(LPCMAudioStreamDescriptorTest, Accessors) {
+ ASSERT_EQ(LPCMAudioStreamDescriptor::kTag, descriptor_.tag());
+ const LPCMAudioStreamDescriptor& descriptor =
+ *static_cast<const LPCMAudioStreamDescriptor*>(&descriptor_);
+ EXPECT_EQ(sampling_frequency_, descriptor.sampling_frequency());
+ EXPECT_EQ(bits_per_sample_, descriptor.bits_per_sample());
+ EXPECT_EQ(emphasis_flag_, descriptor.emphasis_flag());
+ EXPECT_EQ(number_of_channels_, descriptor.number_of_channels());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ WiFiDisplayElementaryStreamDescriptorTests,
+ LPCMAudioStreamDescriptorTest,
+ testing::Values(testing::make_tuple(
+ LPCMAudioStreamDescriptor::SAMPLING_FREQUENCY_44_1K,
+ LPCMAudioStreamDescriptor::BITS_PER_SAMPLE_16,
+ false,
+ LPCMAudioStreamDescriptor::NUMBER_OF_CHANNELS_DUAL_MONO,
+ Data("\x83\x02\x26\x0F")),
+ testing::make_tuple(
+ LPCMAudioStreamDescriptor::SAMPLING_FREQUENCY_48K,
+ LPCMAudioStreamDescriptor::BITS_PER_SAMPLE_16,
+ false,
+ LPCMAudioStreamDescriptor::NUMBER_OF_CHANNELS_DUAL_MONO,
+ Data("\x83\x02\x46\x0F")),
+ testing::make_tuple(
+ LPCMAudioStreamDescriptor::SAMPLING_FREQUENCY_44_1K,
+ LPCMAudioStreamDescriptor::BITS_PER_SAMPLE_16,
+ true,
+ LPCMAudioStreamDescriptor::NUMBER_OF_CHANNELS_DUAL_MONO,
+ Data("\x83\x02\x27\x0F")),
+ testing::make_tuple(
+ LPCMAudioStreamDescriptor::SAMPLING_FREQUENCY_44_1K,
+ LPCMAudioStreamDescriptor::BITS_PER_SAMPLE_16,
+ false,
+ LPCMAudioStreamDescriptor::NUMBER_OF_CHANNELS_STEREO,
+ Data("\x83\x02\x26\x2F"))));
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc
new file mode 100644
index 00000000000..2c84885db87
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc
@@ -0,0 +1,46 @@
+// Copyright 2016 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.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h"
+
+#include <utility>
+
+namespace extensions {
+
+WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo(
+ ElementaryStreamType type)
+ : type_(type) {}
+
+WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo(
+ ElementaryStreamType type,
+ DescriptorVector descriptors)
+ : descriptors_(std::move(descriptors)), type_(type) {}
+
+WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo(
+ const WiFiDisplayElementaryStreamInfo&) = default;
+
+WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo(
+ WiFiDisplayElementaryStreamInfo&&) = default;
+
+WiFiDisplayElementaryStreamInfo::~WiFiDisplayElementaryStreamInfo() {}
+
+WiFiDisplayElementaryStreamInfo& WiFiDisplayElementaryStreamInfo::operator=(
+ WiFiDisplayElementaryStreamInfo&&) = default;
+
+void WiFiDisplayElementaryStreamInfo::AddDescriptor(
+ WiFiDisplayElementaryStreamDescriptor descriptor) {
+ descriptors_.emplace_back(std::move(descriptor));
+}
+
+const WiFiDisplayElementaryStreamDescriptor*
+WiFiDisplayElementaryStreamInfo::FindDescriptor(
+ DescriptorTag descriptor_tag) const {
+ for (const auto& descriptor : descriptors()) {
+ if (descriptor.tag() == descriptor_tag)
+ return &descriptor;
+ }
+ return nullptr;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h
new file mode 100644
index 00000000000..a0e63a82b01
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h
@@ -0,0 +1,58 @@
+// Copyright 2016 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.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_INFO_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_INFO_H_
+
+#include <stdint.h>
+#include <vector>
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h"
+
+namespace extensions {
+
+// WiFi Display elementary stream info is a container for elementary stream
+// information and is used for passing that information to a WiFi Display
+// transport stream packetizer.
+class WiFiDisplayElementaryStreamInfo {
+ public:
+ using DescriptorVector = std::vector<WiFiDisplayElementaryStreamDescriptor>;
+ using DescriptorTag = WiFiDisplayElementaryStreamDescriptor::DescriptorTag;
+
+ enum ElementaryStreamType : uint8_t {
+ AUDIO_AAC = 0x0Fu,
+ AUDIO_AC3 = 0x81u,
+ AUDIO_LPCM = 0x83u,
+ VIDEO_H264 = 0x1Bu,
+ };
+
+ explicit WiFiDisplayElementaryStreamInfo(ElementaryStreamType type);
+ WiFiDisplayElementaryStreamInfo(ElementaryStreamType type,
+ DescriptorVector descriptors);
+ WiFiDisplayElementaryStreamInfo(const WiFiDisplayElementaryStreamInfo&);
+ WiFiDisplayElementaryStreamInfo(WiFiDisplayElementaryStreamInfo&&);
+ ~WiFiDisplayElementaryStreamInfo();
+
+ WiFiDisplayElementaryStreamInfo& operator=(WiFiDisplayElementaryStreamInfo&&);
+
+ const DescriptorVector& descriptors() const { return descriptors_; }
+ ElementaryStreamType type() const { return type_; }
+
+ void AddDescriptor(WiFiDisplayElementaryStreamDescriptor descriptor);
+ const WiFiDisplayElementaryStreamDescriptor* FindDescriptor(
+ DescriptorTag descriptor_tag) const;
+ template <typename Descriptor>
+ const Descriptor* FindDescriptor() const {
+ return static_cast<const Descriptor*>(
+ FindDescriptor(static_cast<DescriptorTag>(Descriptor::kTag)));
+ }
+
+ private:
+ DescriptorVector descriptors_;
+ ElementaryStreamType type_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_INFO_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.cc
new file mode 100644
index 00000000000..39a6e3fcaf8
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.cc
@@ -0,0 +1,188 @@
+// Copyright 2016 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.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h"
+
+#include <cstring>
+
+#include "base/logging.h"
+
+namespace extensions {
+namespace {
+
+// Code and parameters related to the Packetized Elementary Stream (PES)
+// specification.
+namespace pes {
+
+const size_t kOptionalHeaderBaseSize = 3u;
+const size_t kPacketHeaderBaseSize = 6u;
+const size_t kTimeStampSize = 5u;
+const size_t kPacketHeaderMaxSize =
+ kPacketHeaderBaseSize + kOptionalHeaderBaseSize + 2u * kTimeStampSize;
+
+const size_t kUnitDataAlignment = 4u;
+
+size_t FillInTimeStamp(uint8_t* dst,
+ uint8_t pts_dts_indicator,
+ const base::TimeTicks& ts) {
+ // Convert to the number of 90 kHz ticks since some epoch.
+ // Always round up so that the number of ticks is never smaller than
+ // the number of program clock reference base ticks (which is not rounded
+ // because program clock reference is encoded with higher precision).
+ const uint64_t us =
+ static_cast<uint64_t>((ts - base::TimeTicks()).InMicroseconds());
+ const uint64_t n = (us * 90u + 999u) / 1000u;
+
+ // Expand PTS DTS indicator and a 33 bit time stamp to 40 bits:
+ // * 4 PTS DTS indicator bits, 3 time stamp bits, 1 on bit
+ // * 15 time stamp bits, 1 on bit
+ // * 15 time stamp bits, 1 on bit
+ size_t i = 0u;
+ dst[i++] = (pts_dts_indicator << 4) | (((n >> 30) & 0x7u) << 1) | (0x1u << 0);
+ dst[i++] = (n >> 22) & 0xFFu;
+ dst[i++] = (((n >> 15) & 0x7Fu) << 1) | (0x1u << 0);
+ dst[i++] = (n >> 7) & 0xFFu;
+ dst[i++] = (((n >> 0) & 0x7Fu) << 1) | (0x1u << 0);
+ DCHECK_EQ(i, kTimeStampSize);
+ return i;
+}
+
+size_t FillInOptionalHeader(uint8_t* dst,
+ const base::TimeTicks& pts,
+ const base::TimeTicks& dts,
+ size_t unit_header_size) {
+ size_t i = 0u;
+ dst[i++] = (0x2u << 6) | // Marker bits (0b10)
+ (0x0u << 4) | // Scrambling control (0b00 for not)
+ (0x0u << 3) | // Priority
+ (0x0u << 2) | // Data alignment indicator (0b0 for not)
+ (0x0u << 1) | // Copyright (0b0 for not)
+ (0x0u << 0); // Original (0b0 for copy)
+ const uint8_t pts_dts_indicator =
+ !pts.is_null() ? (!dts.is_null() ? 0x3u : 0x2u) : 0x0u;
+ dst[i++] = (pts_dts_indicator << 6) | // PTS DTS indicator
+ (0x0u << 5) | // ESCR flag
+ (0x0u << 4) | // ES rate flag
+ (0x0u << 3) | // DSM trick mode flag
+ (0x0u << 2) | // Additional copy info flag
+ (0x0u << 1) | // CRC flag
+ (0x0u << 0); // Extension flag
+ const size_t header_length_index = i++;
+ const size_t optional_header_base_end_index = i;
+ DCHECK_EQ(i, kOptionalHeaderBaseSize);
+
+ // Optional fields:
+ // PTS and DTS.
+ if (!pts.is_null()) {
+ i += FillInTimeStamp(&dst[i], pts_dts_indicator, pts);
+ if (!dts.is_null())
+ i += FillInTimeStamp(&dst[i], 0x1u, dts);
+ }
+
+ // Stuffing bytes (for unit data alignment).
+ const size_t remainder =
+ (kPacketHeaderBaseSize + i + unit_header_size) % kUnitDataAlignment;
+ if (remainder) {
+ const size_t n = kUnitDataAlignment - remainder;
+ std::memset(&dst[i], 0xFF, n);
+ i += n;
+ }
+
+ dst[header_length_index] = i - optional_header_base_end_index;
+ return i;
+}
+
+size_t FillInPacketHeader(uint8_t* dst,
+ uint8_t stream_id,
+ const base::TimeTicks& pts,
+ const base::TimeTicks& dts,
+ size_t unit_header_size,
+ size_t unit_size) {
+ // Reserve space for packet header base.
+ size_t i = kPacketHeaderBaseSize;
+ const size_t header_base_end_index = i;
+
+ // Fill in optional header.
+ i += FillInOptionalHeader(&dst[i], pts, dts, unit_header_size);
+
+ // Compute packet length.
+ size_t packet_length =
+ (i - header_base_end_index) + unit_header_size + unit_size;
+ if (packet_length >> 16) {
+ // The packet length is too large to be represented. That should only
+ // happen for video frames for which the packet length is not mandatory
+ // but may be set to 0, too.
+ DCHECK_GE(static_cast<unsigned>(stream_id),
+ WiFiDisplayElementaryStreamPacketizer::kFirstVideoStreamId);
+ DCHECK_LE(static_cast<unsigned>(stream_id),
+ WiFiDisplayElementaryStreamPacketizer::kLastVideoStreamId);
+ packet_length = 0u;
+ }
+
+ // Fill in packet header base.
+ size_t j = 0u;
+ dst[j++] = 0x00u; // Packet start code prefix (0x000001 in three bytes).
+ dst[j++] = 0x00u; //
+ dst[j++] = 0x01u; //
+ dst[j++] = stream_id;
+ dst[j++] = packet_length >> 8;
+ dst[j++] = packet_length & 0xFFu;
+ DCHECK_EQ(j, kPacketHeaderBaseSize);
+
+ return i;
+}
+
+} // namespace pes
+
+} // namespace
+
+WiFiDisplayElementaryStreamPacket::WiFiDisplayElementaryStreamPacket(
+ const HeaderBuffer& header_data,
+ size_t header_size,
+ const uint8_t* unit_header_data,
+ size_t unit_header_size,
+ const uint8_t* unit_data,
+ size_t unit_size)
+ : header_(header_buffer_, header_size),
+ unit_header_(unit_header_data, unit_header_size),
+ unit_(unit_data, unit_size) {
+ // Copy the actual header data bytes from the |header_data| argument to
+ // the |header_buffer_| member buffer used in the member initialization list.
+ std::memcpy(header_buffer_, header_data, header_.size());
+}
+
+WiFiDisplayElementaryStreamPacket::WiFiDisplayElementaryStreamPacket(
+ WiFiDisplayElementaryStreamPacket&& other)
+ : header_(header_buffer_, other.header().size()),
+ unit_header_(other.unit_header().data(), other.unit_header().size()),
+ unit_(other.unit().data(), other.unit().size()) {
+ // Copy the actual header data bytes from |other.header().data()| to
+ // the |header_buffer_| member buffer used in the member initialization list.
+ std::memcpy(header_buffer_, other.header().data(), header_.size());
+}
+
+// static
+WiFiDisplayElementaryStreamPacket
+WiFiDisplayElementaryStreamPacketizer::EncodeElementaryStreamUnit(
+ uint8_t stream_id,
+ const uint8_t* unit_header_data,
+ size_t unit_header_size,
+ const uint8_t* unit_data,
+ size_t unit_size,
+ const base::TimeTicks& pts,
+ const base::TimeTicks& dts) {
+ if (!unit_header_data) {
+ DCHECK_EQ(0u, unit_header_size);
+ unit_header_data = unit_data;
+ }
+
+ uint8_t header_data[pes::kPacketHeaderMaxSize];
+ size_t header_size = pes::FillInPacketHeader(header_data, stream_id, pts, dts,
+ unit_header_size, unit_size);
+ return WiFiDisplayElementaryStreamPacket(header_data, header_size,
+ unit_header_data, unit_header_size,
+ unit_data, unit_size);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h
new file mode 100644
index 00000000000..4201a58287d
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h
@@ -0,0 +1,72 @@
+// Copyright 2016 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.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_PACKETIZER_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_PACKETIZER_H_
+
+#include "base/time/time.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h"
+
+namespace extensions {
+
+// WiFi Display elementary stream packet represents a Packetized Elementary
+// Stream (PES) packet containing WiFi Display elementary stream unit data.
+class WiFiDisplayElementaryStreamPacket {
+ public:
+ using HeaderBuffer = uint8_t[19];
+
+ WiFiDisplayElementaryStreamPacket(const HeaderBuffer& header_data,
+ size_t header_size,
+ const uint8_t* unit_header_data,
+ size_t unit_header_size,
+ const uint8_t* unit_data,
+ size_t unit_size);
+ // WiFiDisplayElementaryStreamPacketizer::EncodeElementaryStreamUnit returns
+ // WiFiDisplayElementaryStreamPacket so WiFiDisplayElementaryStreamPacket
+ // must be move constructible (as it is not copy constructible).
+ // A compiler should however use return value optimization and elide each
+ // call to this move constructor.
+ WiFiDisplayElementaryStreamPacket(WiFiDisplayElementaryStreamPacket&& other);
+
+ const WiFiDisplayStreamPacketPart& header() const { return header_; }
+ const WiFiDisplayStreamPacketPart& unit_header() const {
+ return unit_header_;
+ }
+ const WiFiDisplayStreamPacketPart& unit() const { return unit_; }
+
+ private:
+ HeaderBuffer header_buffer_;
+ WiFiDisplayStreamPacketPart header_;
+ WiFiDisplayStreamPacketPart unit_header_;
+ WiFiDisplayStreamPacketPart unit_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplayElementaryStreamPacket);
+};
+
+// The WiFi Display elementary stream packetizer packetizes unit buffers to
+// Packetized Elementary Stream (PES) packets.
+// It is used internally by a WiFi Display transport stream packetizer.
+class WiFiDisplayElementaryStreamPacketizer {
+ public:
+ enum : uint8_t {
+ kPrivateStream1Id = 0xBDu,
+ kFirstAudioStreamId = 0xC0u,
+ kLastAudioStreamId = 0xDFu,
+ kFirstVideoStreamId = 0xE0u,
+ kLastVideoStreamId = 0xEFu,
+ };
+
+ static WiFiDisplayElementaryStreamPacket EncodeElementaryStreamUnit(
+ uint8_t stream_id,
+ const uint8_t* unit_header_data,
+ size_t unit_header_size,
+ const uint8_t* unit_data,
+ size_t unit_size,
+ const base::TimeTicks& pts,
+ const base::TimeTicks& dts);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_PACKETIZER_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.cc
new file mode 100644
index 00000000000..f77571d58a2
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.cc
@@ -0,0 +1,247 @@
+// Copyright 2016 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.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h"
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "content/public/renderer/media_stream_utils.h"
+
+namespace extensions {
+
+namespace {
+
+const char kErrorNoVideoFormatData[] =
+ "Failed to get video format data from the given MediaStreamTrack object";
+const char kErrorSinkCannotPlayVideo[] =
+ "The sink cannot play video from the given MediaStreamTrack object";
+const char kErrorSinkCannotPlayAudio[] =
+ "The sink cannot play audio from the given MediaStreamTrack object";
+} // namespace
+
+WiFiDisplayMediaManager::WiFiDisplayMediaManager(
+ const blink::WebMediaStreamTrack& video_track,
+ const blink::WebMediaStreamTrack& audio_track,
+ const ErrorCallback& error_callback)
+ : video_track_(video_track),
+ audio_track_(audio_track),
+ error_callback_(error_callback) {
+ DCHECK(!video_track.isNull() || !audio_track.isNull());
+ DCHECK(!error_callback_.is_null());
+}
+
+WiFiDisplayMediaManager::~WiFiDisplayMediaManager() {
+}
+
+void WiFiDisplayMediaManager::Play() {
+ NOTIMPLEMENTED();
+}
+
+void WiFiDisplayMediaManager::Teardown() {
+ NOTIMPLEMENTED();
+}
+
+void WiFiDisplayMediaManager::Pause() {
+ NOTIMPLEMENTED();
+}
+
+bool WiFiDisplayMediaManager::IsPaused() const {
+ NOTIMPLEMENTED();
+ return true;
+}
+
+wds::SessionType WiFiDisplayMediaManager::GetSessionType() const {
+ uint16_t session_type = 0;
+ if (!video_track_.isNull())
+ session_type |= wds::VideoSession;
+
+ if (!audio_track_.isNull())
+ session_type |= wds::AudioSession;
+
+ return static_cast<wds::SessionType>(session_type);
+}
+
+void WiFiDisplayMediaManager::SetSinkRtpPorts(int port1, int port2) {
+ sink_rtp_ports_ = std::pair<int, int>(port1, port2);
+}
+
+std::pair<int, int> WiFiDisplayMediaManager::GetSinkRtpPorts() const {
+ return sink_rtp_ports_;
+}
+
+int WiFiDisplayMediaManager::GetLocalRtpPort() const {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+namespace {
+struct VideoFormat {
+ wds::RateAndResolution rr;
+ int width;
+ int height;
+ int frame_rate;
+};
+
+const VideoFormat cea_table[] = {
+ {wds::CEA640x480p60, 640, 480, 60},
+ {wds::CEA720x480p60, 720, 480, 60},
+ {wds::CEA720x576p50, 720, 576, 50},
+ {wds::CEA1280x720p30, 1280, 720, 30},
+ {wds::CEA1280x720p60, 1280, 720, 60},
+ {wds::CEA1920x1080p30, 1920, 1080, 30},
+ {wds::CEA1920x1080p60, 1920, 1080, 60},
+ {wds::CEA1280x720p25, 1280, 720, 25},
+ {wds::CEA1280x720p50, 1280, 720, 50},
+ {wds::CEA1920x1080p25, 1920, 1080, 25},
+ {wds::CEA1920x1080p50, 1920, 1080, 50},
+ {wds::CEA1280x720p24, 1280, 720, 24},
+ {wds::CEA1920x1080p24, 1920, 1080, 24}
+};
+
+const VideoFormat vesa_table[] = {
+ {wds::VESA800x600p30, 800, 600, 30},
+ {wds::VESA800x600p60, 800, 600, 60},
+ {wds::VESA1024x768p30, 1024, 768, 30},
+ {wds::VESA1024x768p60, 1024, 768, 60},
+ {wds::VESA1152x864p30, 1152, 864, 30},
+ {wds::VESA1152x864p60, 1152, 864, 60},
+ {wds::VESA1280x768p30, 1280, 768, 30},
+ {wds::VESA1280x768p60, 1280, 768, 60},
+ {wds::VESA1280x800p30, 1280, 800, 30},
+ {wds::VESA1280x800p60, 1280, 800, 60},
+ {wds::VESA1360x768p30, 1360, 768, 30},
+ {wds::VESA1360x768p60, 1360, 768, 60},
+ {wds::VESA1366x768p30, 1366, 768, 30},
+ {wds::VESA1366x768p60, 1366, 768, 60},
+ {wds::VESA1280x1024p30, 1280, 1024, 30},
+ {wds::VESA1280x1024p60, 1280, 1024, 60},
+ {wds::VESA1400x1050p30, 1400, 1050, 30},
+ {wds::VESA1400x1050p60, 1400, 1050, 60},
+ {wds::VESA1440x900p30, 1440, 900, 30},
+ {wds::VESA1440x900p60, 1440, 900, 60},
+ {wds::VESA1600x900p30, 1600, 900, 30},
+ {wds::VESA1600x900p60, 1600, 900, 60},
+ {wds::VESA1600x1200p30, 1600, 1200, 30},
+ {wds::VESA1600x1200p60, 1600, 1200, 60},
+ {wds::VESA1680x1024p30, 1680, 1024, 30},
+ {wds::VESA1680x1024p60, 1680, 1024, 60},
+ {wds::VESA1680x1050p30, 1680, 1050, 30},
+ {wds::VESA1680x1050p60, 1680, 1050, 60},
+ {wds::VESA1920x1200p30, 1920, 1200, 30}
+};
+
+const VideoFormat hh_table[] = {
+ {wds::HH800x480p30, 800, 480, 30},
+ {wds::HH800x480p60, 800, 480, 60},
+ {wds::HH854x480p30, 854, 480, 30},
+ {wds::HH854x480p60, 854, 480, 60},
+ {wds::HH864x480p30, 864, 480, 30},
+ {wds::HH864x480p60, 864, 480, 60},
+ {wds::HH640x360p30, 640, 360, 30},
+ {wds::HH640x360p60, 640, 360, 60},
+ {wds::HH960x540p30, 960, 540, 30},
+ {wds::HH960x540p60, 960, 540, 60},
+ {wds::HH848x480p30, 848, 480, 30},
+ {wds::HH848x480p60, 848, 480, 60}
+};
+
+template <wds::ResolutionType type, unsigned N>
+bool FindRateResolution(const media::VideoCaptureFormat* format,
+ const wds::RateAndResolutionsBitmap& bitmap,
+ const VideoFormat (&table)[N],
+ wds::H264VideoFormat* result /*out*/) {
+ for (unsigned i = 0; i < N; ++i) {
+ if (bitmap.test(table[i].rr)) {
+ if (format->frame_size.width() == table[i].width &&
+ format->frame_size.height() == table[i].height &&
+ format->frame_rate == table[i].frame_rate) {
+ result->rate_resolution = table[i].rr;
+ result->type = type;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool FindOptimalFormat(
+ const media::VideoCaptureFormat* capture_format,
+ const std::vector<wds::H264VideoCodec>& sink_supported_codecs,
+ wds::H264VideoFormat* result /*out*/) {
+ DCHECK(result);
+ for (const wds::H264VideoCodec& codec : sink_supported_codecs) {
+ bool found =
+ FindRateResolution<wds::CEA>(
+ capture_format, codec.cea_rr, cea_table, result) ||
+ FindRateResolution<wds::VESA>(
+ capture_format, codec.vesa_rr, vesa_table, result) ||
+ FindRateResolution<wds::HH>(
+ capture_format, codec.hh_rr, hh_table, result);
+ if (found) {
+ result->profile = codec.profile;
+ result->level = codec.level;
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+wds::H264VideoFormat WiFiDisplayMediaManager::GetOptimalVideoFormat() const {
+ return optimal_video_format_;
+}
+
+void WiFiDisplayMediaManager::SendIDRPicture() {
+ NOTIMPLEMENTED();
+}
+
+std::string WiFiDisplayMediaManager::GetSessionId() const {
+ return base::RandBytesAsString(8);
+}
+
+bool WiFiDisplayMediaManager::InitOptimalVideoFormat(
+ const wds::NativeVideoFormat& sink_native_format,
+ const std::vector<wds::H264VideoCodec>& sink_supported_codecs) {
+ const media::VideoCaptureFormat* capture_format =
+ content::GetCurrentVideoTrackFormat(video_track_);
+ if (!capture_format) {
+ error_callback_.Run(kErrorNoVideoFormatData);
+ return false;
+ }
+
+ if (!FindOptimalFormat(
+ capture_format, sink_supported_codecs, &optimal_video_format_)) {
+ error_callback_.Run(kErrorSinkCannotPlayVideo);
+ return false;
+ }
+
+ return true;
+}
+
+bool WiFiDisplayMediaManager::InitOptimalAudioFormat(
+ const std::vector<wds::AudioCodec>& sink_codecs) {
+ for (const wds::AudioCodec& codec : sink_codecs) {
+ // MediaStreamTrack contains LPCM audio.
+ if (codec.format == wds::LPCM) {
+ optimal_audio_codec_ = codec;
+ // Picking a single mode.
+ wds::AudioModes optimal_mode;
+ if (codec.modes.test(wds::LPCM_44_1K_16B_2CH))
+ optimal_mode.set(wds::LPCM_44_1K_16B_2CH);
+ else
+ optimal_mode.set(wds::LPCM_48K_16B_2CH);
+ optimal_audio_codec_.modes = optimal_mode;
+ return true;
+ }
+ }
+ error_callback_.Run(kErrorSinkCannotPlayAudio);
+ return false;
+}
+
+wds::AudioCodec WiFiDisplayMediaManager::GetOptimalAudioFormat() const {
+ return optimal_audio_codec_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h
new file mode 100644
index 00000000000..277771cb2bc
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h
@@ -0,0 +1,69 @@
+// Copyright 2016 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.
+
+#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_MANAGER_H_
+#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_MANAGER_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "third_party/WebKit/public/web/WebDOMMediaStreamTrack.h"
+#include "third_party/wds/src/libwds/public/media_manager.h"
+
+namespace extensions {
+
+class WiFiDisplayMediaManager : public wds::SourceMediaManager {
+ public:
+ using ErrorCallback = base::Callback<void(const std::string&)>;
+
+ WiFiDisplayMediaManager(
+ const blink::WebMediaStreamTrack& video_track,
+ const blink::WebMediaStreamTrack& audio_track,
+ const ErrorCallback& error_callback);
+
+ ~WiFiDisplayMediaManager() override;
+
+ private:
+ // wds::SourceMediaManager overrides.
+ void Play() override;
+
+ void Pause() override;
+ void Teardown() override;
+ bool IsPaused() const override;
+ wds::SessionType GetSessionType() const override;
+ void SetSinkRtpPorts(int port1, int port2) override;
+ std::pair<int, int> GetSinkRtpPorts() const override;
+ int GetLocalRtpPort() const override;
+
+ bool InitOptimalVideoFormat(
+ const wds::NativeVideoFormat& sink_native_format,
+ const std::vector<wds::H264VideoCodec>& sink_supported_codecs) override;
+ wds::H264VideoFormat GetOptimalVideoFormat() const override;
+ bool InitOptimalAudioFormat(
+ const std::vector<wds::AudioCodec>& sink_supported_codecs) override;
+ wds::AudioCodec GetOptimalAudioFormat() const override;
+
+ void SendIDRPicture() override;
+
+ std::string GetSessionId() const override;
+
+ private:
+ blink::WebMediaStreamTrack video_track_;
+ blink::WebMediaStreamTrack audio_track_;
+
+ std::pair<int, int> sink_rtp_ports_;
+ wds::H264VideoFormat optimal_video_format_;
+ wds::AudioCodec optimal_audio_codec_;
+
+ ErrorCallback error_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplayMediaManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_MANAGER_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc
new file mode 100644
index 00000000000..f8f9cf23b5d
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc
@@ -0,0 +1,110 @@
+// Copyright 2016 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.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "crypto/random.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h"
+
+namespace extensions {
+namespace {
+const size_t kMaxTransportStreamPacketCount = 7u;
+const uint8_t kProtocolPayloadTypeMP2T = 33u;
+const uint8_t kProtocolVersion = 2u;
+} // namespace
+
+WiFiDisplayMediaDatagramPacket::WiFiDisplayMediaDatagramPacket() = default;
+
+WiFiDisplayMediaDatagramPacket::WiFiDisplayMediaDatagramPacket(
+ WiFiDisplayMediaDatagramPacket&&) = default;
+
+WiFiDisplayMediaPacketizer::WiFiDisplayMediaPacketizer(
+ const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos,
+ const PacketizedCallback& on_packetized)
+ : WiFiDisplayTransportStreamPacketizer(delay_for_unit_time_stamps,
+ std::move(stream_infos)),
+ on_packetized_media_datagram_packet_(on_packetized) {
+ // Sequence numbers are mainly used for detecting lossed packets within one
+ // RTP session. The initial value SHOULD be random (unpredictable) to make
+ // known-plaintext attacks on encryption more difficult, in case the packets
+ // flow through a translator that encrypts them.
+ crypto::RandBytes(&sequence_number_, sizeof(sequence_number_));
+ base::RandBytes(&synchronization_source_identifier_,
+ sizeof(synchronization_source_identifier_));
+}
+
+WiFiDisplayMediaPacketizer::~WiFiDisplayMediaPacketizer() {}
+
+bool WiFiDisplayMediaPacketizer::OnPacketizedTransportStreamPacket(
+ const WiFiDisplayTransportStreamPacket& transport_stream_packet,
+ bool flush) {
+ DCHECK(CalledOnValidThread());
+
+ if (media_datagram_packet_.empty()) {
+ // Convert time to the number of 90 kHz ticks since some epoch.
+ const uint64_t us = static_cast<uint64_t>(
+ (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds());
+ const uint64_t time_stamp = (us * 90u + 500u) / 1000u;
+ const uint8_t header_without_identifiers[] = {
+ (kProtocolVersion << 6 | // Version (2 bits)
+ 0x0u << 5 | // Padding (no)
+ 0x0u << 4 | // Extension (no)
+ 0u << 0), // CSRC count (4 bits)
+ (0x0u << 7 | // Marker (no)
+ kProtocolPayloadTypeMP2T << 0), // Payload type (7 bits)
+ sequence_number_ >> 8, // Sequence number (16 bits)
+ sequence_number_ & 0xFFu, //
+ (time_stamp >> 24) & 0xFFu, // Time stamp (32 bits)
+ (time_stamp >> 16) & 0xFFu, //
+ (time_stamp >> 8) & 0xFFu, //
+ time_stamp & 0xFFu}; //
+ ++sequence_number_;
+ media_datagram_packet_.reserve(
+ sizeof(header_without_identifiers) +
+ sizeof(synchronization_source_identifier_) +
+ kMaxTransportStreamPacketCount *
+ WiFiDisplayTransportStreamPacket::kPacketSize);
+ media_datagram_packet_.insert(media_datagram_packet_.end(),
+ std::begin(header_without_identifiers),
+ std::end(header_without_identifiers));
+ media_datagram_packet_.insert(
+ media_datagram_packet_.end(),
+ std::begin(synchronization_source_identifier_),
+ std::end(synchronization_source_identifier_));
+ DCHECK_EQ(0u, media_datagram_packet_.size() /
+ WiFiDisplayTransportStreamPacket::kPacketSize);
+ }
+
+ // Append header and payload data.
+ media_datagram_packet_.insert(media_datagram_packet_.end(),
+ transport_stream_packet.header().begin(),
+ transport_stream_packet.header().end());
+ media_datagram_packet_.insert(media_datagram_packet_.end(),
+ transport_stream_packet.payload().begin(),
+ transport_stream_packet.payload().end());
+ media_datagram_packet_.insert(media_datagram_packet_.end(),
+ transport_stream_packet.filler().size(),
+ transport_stream_packet.filler().value());
+
+ // Combine multiple transport stream packets into one datagram packet
+ // by delaying delegation whenever possible.
+ if (!flush) {
+ const size_t transport_stream_packet_count =
+ media_datagram_packet_.size() /
+ WiFiDisplayTransportStreamPacket::kPacketSize;
+ if (transport_stream_packet_count < kMaxTransportStreamPacketCount)
+ return true;
+ }
+
+ WiFiDisplayMediaDatagramPacket packet;
+ packet.swap(media_datagram_packet_);
+ return on_packetized_media_datagram_packet_.Run(std::move(packet));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h
new file mode 100644
index 00000000000..23c07e08119
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h
@@ -0,0 +1,61 @@
+// Copyright 2016 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.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_PACKETIZER_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_PACKETIZER_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/move.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h"
+
+namespace extensions {
+
+// This class represents an RTP datagram packet containing MPEG Transport
+// Stream (MPEG-TS) packets as a payload.
+class WiFiDisplayMediaDatagramPacket : public std::vector<uint8_t> {
+ public:
+ WiFiDisplayMediaDatagramPacket();
+ WiFiDisplayMediaDatagramPacket(WiFiDisplayMediaDatagramPacket&&);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN_WITH_MOVE_FOR_BIND(WiFiDisplayMediaDatagramPacket);
+};
+
+// The WiFi Display media packetizer packetizes unit buffers to media datagram
+// packets containing MPEG Transport Stream (MPEG-TS) packets containing either
+// meta information or Packetized Elementary Stream (PES) packets containing
+// unit data.
+//
+// Whenever a media datagram packet is fully created and thus ready for further
+// processing, a callback is called.
+class WiFiDisplayMediaPacketizer : public WiFiDisplayTransportStreamPacketizer {
+ public:
+ using PacketizedCallback =
+ base::Callback<bool(WiFiDisplayMediaDatagramPacket)>;
+
+ WiFiDisplayMediaPacketizer(
+ const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos,
+ const PacketizedCallback& on_packetized);
+ ~WiFiDisplayMediaPacketizer() override;
+
+ protected:
+ bool OnPacketizedTransportStreamPacket(
+ const WiFiDisplayTransportStreamPacket& transport_stream_packet,
+ bool flush) override;
+
+ private:
+ using SourceIdentifier = uint8_t[4];
+
+ WiFiDisplayMediaDatagramPacket media_datagram_packet_;
+ PacketizedCallback on_packetized_media_datagram_packet_;
+ uint16_t sequence_number_;
+ SourceIdentifier synchronization_source_identifier_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_PACKETIZER_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc
new file mode 100644
index 00000000000..5253a1baa76
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc
@@ -0,0 +1,905 @@
+// Copyright 2016 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.
+
+#include <algorithm>
+#include <array>
+#include <list>
+
+#include "base/big_endian.h"
+#include "base/bind.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using PacketPart = extensions::WiFiDisplayStreamPacketPart;
+
+namespace extensions {
+
+std::ostream& operator<<(std::ostream& os, const PacketPart& part) {
+ const auto flags = os.flags();
+ os << "{" << std::hex << std::noshowbase;
+ for (const auto& item : part) {
+ if (&item != &*part.begin())
+ os << ", ";
+ os << "0x" << static_cast<unsigned>(item);
+ }
+ os.setf(flags, std::ios::basefield | std::ios::showbase);
+ return os << "}";
+}
+
+bool operator==(const PacketPart& a, const PacketPart& b) {
+ if (a.size() != b.size())
+ return false;
+ return std::equal(a.begin(), a.end(), b.begin());
+}
+
+namespace {
+
+namespace pes {
+const unsigned kDtsFlag = 0x0040u;
+const unsigned kMarkerFlag = 0x8000u;
+const unsigned kPtsFlag = 0x0080u;
+const size_t kUnitDataAlignment = sizeof(uint32_t);
+}
+
+namespace rtp {
+const unsigned kVersionMask = 0xC000u;
+const unsigned kVersion2 = 0x8000u;
+const unsigned kPaddingFlag = 0x2000u;
+const unsigned kExtensionFlag = 0x1000u;
+const unsigned kContributingSourceCountMask = 0x0F00u;
+const unsigned kMarkerFlag = 0x0010u;
+const unsigned kPayloadTypeMask = 0x007Fu;
+const unsigned kPayloadTypeMP2T = 0x0021u;
+} // namespace rtp
+
+namespace ts {
+const uint64_t kTimeStampMask = (static_cast<uint64_t>(1u) << 33) - 1u;
+const uint64_t kTimeStampSecond = 90000u; // 90 kHz
+const uint64_t kProgramClockReferenceSecond =
+ 300u * kTimeStampSecond; // 27 MHz
+
+// Packet header:
+const size_t kPacketHeaderSize = 4u;
+const unsigned kSyncByte = 0x47u;
+const uint32_t kSyncByteMask = 0xFF000000u;
+const uint32_t kTransportErrorIndicator = 0x00800000u;
+const uint32_t kPayloadUnitStartIndicator = 0x00400000u;
+const uint32_t kTransportPriority = 0x00200000u;
+const uint32_t kScramblingControlMask = 0x000000C0u;
+const uint32_t kAdaptationFieldFlag = 0x00000020u;
+const uint32_t kPayloadFlag = 0x00000010u;
+
+// Adaptation field:
+const unsigned kRandomAccessFlag = 0x40u;
+const unsigned kPcrFlag = 0x10u;
+} // namespace ts
+
+namespace widi {
+const unsigned kProgramAssociationTablePacketId = 0x0000u;
+const unsigned kProgramMapTablePacketId = 0x0100u;
+const unsigned kProgramClockReferencePacketId = 0x1000u;
+const unsigned kVideoStreamPacketId = 0x1011u;
+const unsigned kFirstAudioStreamPacketId = 0x1100u;
+const size_t kMaxTransportStreamPacketCountPerDatagramPacket = 7u;
+} // namespace widi
+
+template <typename PacketContainer>
+class PacketCollector {
+ public:
+ PacketContainer FetchPackets() {
+ PacketContainer container;
+ container.swap(packets_);
+ return container;
+ }
+
+ protected:
+ PacketContainer packets_;
+};
+
+class FakeMediaPacketizer
+ : public WiFiDisplayMediaPacketizer,
+ public PacketCollector<std::vector<std::vector<uint8_t>>> {
+ public:
+ FakeMediaPacketizer(const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos)
+ : WiFiDisplayMediaPacketizer(
+ delay_for_unit_time_stamps,
+ std::move(stream_infos),
+ base::Bind(&FakeMediaPacketizer::OnPacketizedMediaDatagramPacket,
+ base::Unretained(this))) {}
+
+ // Extend the interface in order to allow to bypass packetization of units to
+ // Packetized Elementary Stream (PES) packets and further to Transport Stream
+ // (TS) packets and to test only packetization of TS packets to media
+ // datagram packets.
+ bool EncodeTransportStreamPacket(
+ const WiFiDisplayTransportStreamPacket& transport_stream_packet,
+ bool flush) {
+ return OnPacketizedTransportStreamPacket(transport_stream_packet, flush);
+ }
+
+ private:
+ bool OnPacketizedMediaDatagramPacket(
+ WiFiDisplayMediaDatagramPacket media_datagram_packet) {
+ packets_.emplace_back(std::move(media_datagram_packet));
+ return true;
+ }
+};
+
+class FakeTransportStreamPacketizer
+ : public WiFiDisplayTransportStreamPacketizer,
+ public PacketCollector<std::list<WiFiDisplayTransportStreamPacket>> {
+ public:
+ FakeTransportStreamPacketizer(
+ const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos)
+ : WiFiDisplayTransportStreamPacketizer(delay_for_unit_time_stamps,
+ std::move(stream_infos)) {}
+
+ using WiFiDisplayTransportStreamPacketizer::NormalizeUnitTimeStamps;
+
+ protected:
+ bool OnPacketizedTransportStreamPacket(
+ const WiFiDisplayTransportStreamPacket& transport_stream_packet,
+ bool flush) override {
+ // Make a copy of header bytes as they are in stack.
+ headers_.emplace_back(transport_stream_packet.header().begin(),
+ transport_stream_packet.header().end());
+ const auto& header = headers_.back();
+ if (transport_stream_packet.payload().empty()) {
+ packets_.emplace_back(header.data(), header.size());
+ } else {
+ packets_.emplace_back(header.data(), header.size(),
+ transport_stream_packet.payload().begin());
+ }
+ EXPECT_EQ(transport_stream_packet.header().size(),
+ packets_.back().header().size());
+ EXPECT_EQ(transport_stream_packet.payload().size(),
+ packets_.back().payload().size());
+ EXPECT_EQ(transport_stream_packet.filler().size(),
+ packets_.back().filler().size());
+ return true;
+ }
+
+ private:
+ std::vector<std::vector<uint8_t>> headers_;
+};
+
+struct ProgramClockReference {
+ enum { kInvalidBase = ~static_cast<uint64_t>(0u) };
+ uint64_t base;
+ uint16_t extension;
+};
+
+ProgramClockReference ParseProgramClockReference(const uint8_t pcr_bytes[6]) {
+ const uint8_t reserved_pcr_bits = pcr_bytes[4] & 0x7Eu;
+ EXPECT_EQ(0x7Eu, reserved_pcr_bits);
+ ProgramClockReference pcr;
+ pcr.base = pcr_bytes[0];
+ pcr.base = (pcr.base << 8) | pcr_bytes[1];
+ pcr.base = (pcr.base << 8) | pcr_bytes[2];
+ pcr.base = (pcr.base << 8) | pcr_bytes[3];
+ pcr.base = (pcr.base << 1) | ((pcr_bytes[4] & 0x80u) >> 7);
+ pcr.extension = pcr_bytes[4] & 0x01u;
+ pcr.extension = (pcr.extension << 8) | pcr_bytes[5];
+ return pcr;
+}
+
+uint64_t ParseTimeStamp(const uint8_t ts_bytes[5], uint8_t pts_dts_indicator) {
+ EXPECT_EQ(pts_dts_indicator, (ts_bytes[0] & 0xF0u) >> 4);
+ EXPECT_EQ(0x01u, ts_bytes[0] & 0x01u);
+ EXPECT_EQ(0x01u, ts_bytes[2] & 0x01u);
+ EXPECT_EQ(0x01u, ts_bytes[4] & 0x01u);
+ uint64_t ts = 0u;
+ ts = (ts_bytes[0] & 0x0Eu) >> 1;
+ ts = (ts << 8) | ts_bytes[1];
+ ts = (ts << 7) | ((ts_bytes[2] & 0xFEu) >> 1);
+ ts = (ts << 8) | ts_bytes[3];
+ ts = (ts << 7) | ((ts_bytes[4] & 0xFEu) >> 1);
+ return ts;
+}
+
+unsigned ParseTransportStreamPacketId(
+ const WiFiDisplayTransportStreamPacket& packet) {
+ if (packet.header().size() < ts::kPacketHeaderSize)
+ return ~0u;
+ return (((packet.header().begin()[1] & 0x001Fu) << 8) |
+ packet.header().begin()[2]);
+}
+
+class WiFiDisplayElementaryStreamUnitPacketizationTest
+ : public testing::TestWithParam<
+ testing::tuple<unsigned, base::TimeDelta, base::TimeDelta>> {
+ protected:
+ static base::TimeTicks SumOrNull(const base::TimeTicks& base,
+ const base::TimeDelta& delta) {
+ return delta.is_max() ? base::TimeTicks() : base + delta;
+ }
+
+ WiFiDisplayElementaryStreamUnitPacketizationTest()
+ : unit_(testing::get<0>(GetParam())),
+ now_(base::TimeTicks::Now()),
+ dts_(SumOrNull(now_, testing::get<1>(GetParam()))),
+ pts_(SumOrNull(now_, testing::get<2>(GetParam()))) {}
+
+ void CheckElementaryStreamPacketHeader(
+ const WiFiDisplayElementaryStreamPacket& packet,
+ uint8_t stream_id) {
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.header().begin()),
+ packet.header().size());
+ uint8_t parsed_packet_start_code_prefix[3];
+ EXPECT_TRUE(
+ header_reader.ReadBytes(parsed_packet_start_code_prefix,
+ sizeof(parsed_packet_start_code_prefix)));
+ EXPECT_EQ(0x00u, parsed_packet_start_code_prefix[0]);
+ EXPECT_EQ(0x00u, parsed_packet_start_code_prefix[1]);
+ EXPECT_EQ(0x01u, parsed_packet_start_code_prefix[2]);
+ uint8_t parsed_stream_id;
+ EXPECT_TRUE(header_reader.ReadU8(&parsed_stream_id));
+ EXPECT_EQ(stream_id, parsed_stream_id);
+ uint16_t parsed_packet_length;
+ EXPECT_TRUE(header_reader.ReadU16(&parsed_packet_length));
+ size_t packet_length = static_cast<size_t>(header_reader.remaining()) +
+ packet.unit_header().size() + packet.unit().size();
+ if (packet_length >> 16)
+ packet_length = 0u;
+ EXPECT_EQ(packet_length, parsed_packet_length);
+ uint16_t parsed_flags;
+ EXPECT_TRUE(header_reader.ReadU16(&parsed_flags));
+ EXPECT_EQ(
+ 0u, parsed_flags & ~(pes::kMarkerFlag | pes::kPtsFlag | pes::kDtsFlag));
+ const bool parsed_pts_flag = (parsed_flags & pes::kPtsFlag) != 0u;
+ const bool parsed_dts_flag = (parsed_flags & pes::kDtsFlag) != 0u;
+ EXPECT_EQ(!pts_.is_null(), parsed_pts_flag);
+ EXPECT_EQ(!pts_.is_null() && !dts_.is_null(), parsed_dts_flag);
+ uint8_t parsed_header_length;
+ EXPECT_TRUE(header_reader.ReadU8(&parsed_header_length));
+ EXPECT_EQ(header_reader.remaining(), parsed_header_length);
+ if (parsed_pts_flag) {
+ uint8_t parsed_pts_bytes[5];
+ EXPECT_TRUE(
+ header_reader.ReadBytes(parsed_pts_bytes, sizeof(parsed_pts_bytes)));
+ const uint64_t parsed_pts =
+ ParseTimeStamp(parsed_pts_bytes, parsed_dts_flag ? 0x3u : 0x2u);
+ if (parsed_dts_flag) {
+ uint8_t parsed_dts_bytes[5];
+ EXPECT_TRUE(header_reader.ReadBytes(parsed_dts_bytes,
+ sizeof(parsed_dts_bytes)));
+ const uint64_t parsed_dts = ParseTimeStamp(parsed_dts_bytes, 0x1u);
+ EXPECT_EQ(
+ static_cast<uint64_t>(90 * (pts_ - dts_).InMicroseconds() / 1000),
+ (parsed_pts - parsed_dts) & UINT64_C(0x1FFFFFFFF));
+ }
+ }
+ while (header_reader.remaining() > 0) {
+ uint8_t parsed_stuffing_byte;
+ EXPECT_TRUE(header_reader.ReadU8(&parsed_stuffing_byte));
+ EXPECT_EQ(0xFFu, parsed_stuffing_byte);
+ }
+ EXPECT_EQ(0, header_reader.remaining());
+ }
+
+ void CheckElementaryStreamPacketUnitHeader(
+ const WiFiDisplayElementaryStreamPacket& packet,
+ const uint8_t* unit_header_data,
+ size_t unit_header_size) {
+ EXPECT_EQ(unit_header_data, packet.unit_header().begin());
+ EXPECT_EQ(unit_header_size, packet.unit_header().size());
+ }
+
+ void CheckElementaryStreamPacketUnit(
+ const WiFiDisplayElementaryStreamPacket& packet) {
+ EXPECT_EQ(0u, (packet.header().size() + packet.unit_header().size()) %
+ pes::kUnitDataAlignment);
+ EXPECT_EQ(unit_.data(), packet.unit().begin());
+ EXPECT_EQ(unit_.size(), packet.unit().size());
+ }
+
+ void CheckTransportStreamPacketHeader(
+ base::BigEndianReader* header_reader,
+ bool expected_payload_unit_start_indicator,
+ unsigned expected_packet_id,
+ bool* adaptation_field_flag,
+ uint8_t expected_continuity_counter) {
+ uint32_t parsed_u32;
+ EXPECT_TRUE(header_reader->ReadU32(&parsed_u32));
+ EXPECT_EQ(ts::kSyncByte << 24u, parsed_u32 & ts::kSyncByteMask);
+ EXPECT_EQ(0x0u, parsed_u32 & ts::kTransportErrorIndicator);
+ EXPECT_EQ(expected_payload_unit_start_indicator,
+ (parsed_u32 & ts::kPayloadUnitStartIndicator) != 0u);
+ EXPECT_EQ(0x0u, parsed_u32 & ts::kTransportPriority);
+ EXPECT_EQ(expected_packet_id, (parsed_u32 & 0x001FFF00) >> 8);
+ EXPECT_EQ(0x0u, parsed_u32 & ts::kScramblingControlMask);
+ if (!adaptation_field_flag) {
+ EXPECT_EQ(0x0u, parsed_u32 & ts::kAdaptationFieldFlag);
+ } else {
+ *adaptation_field_flag = (parsed_u32 & ts::kAdaptationFieldFlag) != 0u;
+ }
+ EXPECT_EQ(ts::kPayloadFlag, parsed_u32 & ts::kPayloadFlag);
+ EXPECT_EQ(expected_continuity_counter & 0xFu, parsed_u32 & 0x0000000Fu);
+ }
+
+ void CheckTransportStreamAdaptationField(
+ base::BigEndianReader* header_reader,
+ const WiFiDisplayTransportStreamPacket& packet,
+ uint8_t* adaptation_field_flags) {
+ uint8_t parsed_adaptation_field_length;
+ EXPECT_TRUE(header_reader->ReadU8(&parsed_adaptation_field_length));
+ if (parsed_adaptation_field_length > 0u) {
+ const int initial_remaining = header_reader->remaining();
+ uint8_t parsed_adaptation_field_flags;
+ EXPECT_TRUE(header_reader->ReadU8(&parsed_adaptation_field_flags));
+ if (!adaptation_field_flags) {
+ EXPECT_EQ(0x0u, parsed_adaptation_field_flags);
+ } else {
+ *adaptation_field_flags = parsed_adaptation_field_flags;
+ if (parsed_adaptation_field_flags & ts::kPcrFlag) {
+ uint8_t parsed_pcr_bytes[6];
+ EXPECT_TRUE(header_reader->ReadBytes(parsed_pcr_bytes,
+ sizeof(parsed_pcr_bytes)));
+ parsed_pcr_ = ParseProgramClockReference(parsed_pcr_bytes);
+ }
+ }
+ size_t remaining_stuffing_length =
+ parsed_adaptation_field_length -
+ static_cast<size_t>(initial_remaining - header_reader->remaining());
+ while (remaining_stuffing_length > 0u && header_reader->remaining() > 0) {
+ // Adaptation field stuffing byte in header_reader.
+ uint8_t parsed_stuffing_byte;
+ EXPECT_TRUE(header_reader->ReadU8(&parsed_stuffing_byte));
+ EXPECT_EQ(0xFFu, parsed_stuffing_byte);
+ --remaining_stuffing_length;
+ }
+ if (packet.payload().empty()) {
+ // Adaptation field stuffing bytes in packet.filler().
+ EXPECT_EQ(remaining_stuffing_length, packet.filler().size());
+ EXPECT_EQ(0xFFu, packet.filler().value());
+ } else {
+ EXPECT_EQ(0u, remaining_stuffing_length);
+ }
+ }
+ }
+
+ void CheckTransportStreamProgramAssociationTablePacket(
+ const WiFiDisplayTransportStreamPacket& packet) {
+ static const uint8_t kProgramAssicationTable[4u + 13u] = {
+ // Pointer:
+ 0u, // Pointer field
+ // Table header:
+ 0x00u, // Table ID (PAT)
+ 0x80u | // Section syntax indicator (0b1 for PAT)
+ 0x00u | // Private bit (0b0 for PAT)
+ 0x30u | // Reserved bits (0b11)
+ 0x00u | // Section length unused bits (0b00)
+ 0u, // Section length (10 bits)
+ 13u, //
+ // Table syntax:
+ 0x00u, // Table ID extension (transport stream ID)
+ 0x01u, //
+ 0xC0u | // Reserved bits (0b11)
+ 0x00u | // Version (0b00000)
+ 0x01u, // Current indicator (0b1)
+ 0u, // Section number
+ 0u, // Last section number
+ // Program association table specific data:
+ 0x00u, // Program number
+ 0x01u, //
+ 0xE0 | // Reserved bits (0b111)
+ 0x01u, // Program map packet ID (13 bits)
+ 0x00, //
+ // CRC:
+ 0xE8u,
+ 0xF9u, 0x5Eu, 0x7Du};
+
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.header().begin()),
+ packet.header().size());
+
+ CheckTransportStreamPacketHeader(
+ &header_reader, true, widi::kProgramAssociationTablePacketId, nullptr,
+ continuity_.program_assication_table++);
+
+ EXPECT_EQ(PacketPart(kProgramAssicationTable),
+ PacketPart(packet.header().end() - header_reader.remaining(),
+ static_cast<size_t>(header_reader.remaining())));
+ EXPECT_TRUE(header_reader.Skip(header_reader.remaining()));
+
+ EXPECT_EQ(0, header_reader.remaining());
+ EXPECT_EQ(0u, packet.payload().size());
+ }
+
+ void CheckTransportStreamProgramMapTablePacket(
+ const WiFiDisplayTransportStreamPacket& packet,
+ const PacketPart& program_map_table) {
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.header().begin()),
+ packet.header().size());
+
+ CheckTransportStreamPacketHeader(&header_reader, true,
+ widi::kProgramMapTablePacketId, nullptr,
+ continuity_.program_map_table++);
+
+ EXPECT_EQ(program_map_table,
+ PacketPart(packet.header().end() - header_reader.remaining(),
+ static_cast<size_t>(header_reader.remaining())));
+ EXPECT_TRUE(header_reader.Skip(header_reader.remaining()));
+
+ EXPECT_EQ(0, header_reader.remaining());
+ EXPECT_EQ(0u, packet.payload().size());
+ }
+
+ void CheckTransportStreamProgramClockReferencePacket(
+ const WiFiDisplayTransportStreamPacket& packet) {
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.header().begin()),
+ packet.header().size());
+
+ bool parsed_adaptation_field_flag;
+ CheckTransportStreamPacketHeader(
+ &header_reader, true, widi::kProgramClockReferencePacketId,
+ &parsed_adaptation_field_flag, continuity_.program_clock_reference++);
+ EXPECT_TRUE(parsed_adaptation_field_flag);
+
+ uint8_t parsed_adaptation_field_flags;
+ CheckTransportStreamAdaptationField(&header_reader, packet,
+ &parsed_adaptation_field_flags);
+ EXPECT_EQ(ts::kPcrFlag, parsed_adaptation_field_flags);
+
+ EXPECT_EQ(0, header_reader.remaining());
+ EXPECT_EQ(0u, packet.payload().size());
+ }
+
+ void CheckTransportStreamElementaryStreamPacket(
+ const WiFiDisplayTransportStreamPacket& packet,
+ const WiFiDisplayElementaryStreamPacket& elementary_stream_packet,
+ unsigned stream_index,
+ unsigned expected_packet_id,
+ bool expected_random_access,
+ const uint8_t** unit_data_pos) {
+ const bool first_transport_stream_packet_for_current_unit =
+ packet.payload().begin() == unit_.data();
+ const bool last_transport_stream_packet_for_current_unit =
+ packet.payload().end() == unit_.data() + unit_.size();
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.header().begin()),
+ packet.header().size());
+
+ bool parsed_adaptation_field_flag;
+ CheckTransportStreamPacketHeader(
+ &header_reader, first_transport_stream_packet_for_current_unit,
+ expected_packet_id, &parsed_adaptation_field_flag,
+ continuity_.elementary_streams[stream_index]++);
+
+ if (first_transport_stream_packet_for_current_unit) {
+ // Random access can only be signified by adaptation field.
+ if (expected_random_access)
+ EXPECT_TRUE(parsed_adaptation_field_flag);
+ // If there is no need for padding nor for a random access indicator,
+ // then there is no need for an adaptation field, either.
+ if (!last_transport_stream_packet_for_current_unit &&
+ !expected_random_access) {
+ EXPECT_FALSE(parsed_adaptation_field_flag);
+ }
+ if (parsed_adaptation_field_flag) {
+ uint8_t parsed_adaptation_field_flags;
+ CheckTransportStreamAdaptationField(&header_reader, packet,
+ &parsed_adaptation_field_flags);
+ EXPECT_EQ(expected_random_access ? ts::kRandomAccessFlag : 0u,
+ parsed_adaptation_field_flags);
+ }
+
+ // Elementary stream header.
+ PacketPart parsed_elementary_stream_packet_header(
+ packet.header().end() - header_reader.remaining(),
+ std::min(elementary_stream_packet.header().size(),
+ static_cast<size_t>(header_reader.remaining())));
+ EXPECT_EQ(elementary_stream_packet.header(),
+ parsed_elementary_stream_packet_header);
+ EXPECT_TRUE(
+ header_reader.Skip(parsed_elementary_stream_packet_header.size()));
+
+ // Elementary stream unit header.
+ PacketPart parsed_unit_header(
+ packet.header().end() - header_reader.remaining(),
+ std::min(elementary_stream_packet.unit_header().size(),
+ static_cast<size_t>(header_reader.remaining())));
+ EXPECT_EQ(elementary_stream_packet.unit_header(), parsed_unit_header);
+ EXPECT_TRUE(header_reader.Skip(parsed_unit_header.size()));
+
+ // Time stamps.
+ if (parsed_elementary_stream_packet_header.size() >= 19u) {
+ uint64_t parsed_dts = ParseTimeStamp(
+ &parsed_elementary_stream_packet_header.begin()[14], 0x1u);
+ // Check that
+ // 0 <= 300 * parsed_dts - parsed_pcr_value <=
+ // kProgramClockReferenceSecond
+ // where
+ // parsed_pcr_value = 300 * parsed_pcr_.base + parsed_pcr_.extension
+ // but allow parsed_pcr_.base and parsed_dts to wrap around in 33 bits.
+ EXPECT_NE(ProgramClockReference::kInvalidBase, parsed_pcr_.base);
+ EXPECT_LE(
+ 300u * ((parsed_dts - parsed_pcr_.base) & ts::kTimeStampMask) -
+ parsed_pcr_.extension,
+ ts::kProgramClockReferenceSecond)
+ << " DTS must be not smaller than PCR!";
+ }
+ } else {
+ // If there is no need for padding, then there is no need for
+ // an adaptation field, either.
+ if (!last_transport_stream_packet_for_current_unit)
+ EXPECT_FALSE(parsed_adaptation_field_flag);
+ if (parsed_adaptation_field_flag) {
+ CheckTransportStreamAdaptationField(&header_reader, packet, nullptr);
+ }
+ }
+ EXPECT_EQ(0, header_reader.remaining());
+
+ // Transport stream packet payload.
+ EXPECT_EQ(*unit_data_pos, packet.payload().begin());
+ if (*unit_data_pos == packet.payload().begin())
+ *unit_data_pos += packet.payload().size();
+
+ // Transport stream packet filler.
+ EXPECT_EQ(0u, packet.filler().size());
+ }
+
+ enum { kVideoOnlyUnitSize = 0x8000u }; // Not exact. Be on the safe side.
+
+ const std::vector<uint8_t> unit_;
+ const base::TimeTicks now_;
+ const base::TimeTicks dts_;
+ const base::TimeTicks pts_;
+
+ struct {
+ size_t program_assication_table;
+ size_t program_map_table;
+ size_t program_clock_reference;
+ size_t elementary_streams[3];
+ } continuity_ = {0u, 0u, 0u, {0u, 0u, 0u}};
+ ProgramClockReference parsed_pcr_ = {ProgramClockReference::kInvalidBase, 0u};
+};
+
+TEST_P(WiFiDisplayElementaryStreamUnitPacketizationTest,
+ EncodeToElementaryStreamPacket) {
+ const size_t kMaxUnitHeaderSize = 4u;
+
+ const uint8_t stream_id =
+ unit_.size() >= kVideoOnlyUnitSize
+ ? WiFiDisplayElementaryStreamPacketizer::kFirstVideoStreamId
+ : WiFiDisplayElementaryStreamPacketizer::kFirstAudioStreamId;
+
+ WiFiDisplayElementaryStreamPacketizer packetizer;
+ uint8_t unit_header_data[kMaxUnitHeaderSize];
+ for (size_t unit_header_size = 0u; unit_header_size <= kMaxUnitHeaderSize;
+ ++unit_header_size) {
+ WiFiDisplayElementaryStreamPacket packet =
+ packetizer.EncodeElementaryStreamUnit(stream_id, unit_header_data,
+ unit_header_size, unit_.data(),
+ unit_.size(), pts_, dts_);
+ CheckElementaryStreamPacketHeader(packet, stream_id);
+ CheckElementaryStreamPacketUnitHeader(packet, unit_header_data,
+ unit_header_size);
+ CheckElementaryStreamPacketUnit(packet);
+ }
+}
+
+TEST_P(WiFiDisplayElementaryStreamUnitPacketizationTest,
+ EncodeToTransportStreamPackets) {
+ enum { kStreamCount = 3u };
+ static const bool kBoolValues[] = {false, true};
+ static const unsigned kPacketIds[kStreamCount] = {
+ widi::kVideoStreamPacketId, widi::kFirstAudioStreamPacketId + 0u,
+ widi::kFirstAudioStreamPacketId + 1u};
+ static const uint8_t kProgramMapTable[4u + 42u] = {
+ // Pointer:
+ 0u, // Pointer field
+ // Table header:
+ 0x02u, // Table ID (PMT)
+ 0x80u | // Section syntax indicator (0b1 for PMT)
+ 0x00u | // Private bit (0b0 for PMT)
+ 0x30u | // Reserved bits (0b11)
+ 0x00u | // Section length unused bits (0b00)
+ 0u, // Section length (10 bits)
+ 42u, //
+ // Table syntax:
+ 0x00u, // Table ID extension (program number)
+ 0x01u, //
+ 0xC0u | // Reserved bits (0b11)
+ 0x00u | // Version (0b00000)
+ 0x01u, // Current indicator (0b1)
+ 0u, // Section number
+ 0u, // Last section number
+ // Program map table specific data:
+ 0xE0u | // Reserved bits (0b111)
+ 0x10u, // Program clock reference packet ID (13 bits)
+ 0x00u, //
+ 0xF0u | // Reserved bits (0b11)
+ 0x00u | // Program info length unused bits
+ 0u, // Program info length (10 bits)
+ 0u, //
+ // Elementary stream specific data:
+ 0x1Bu, // Stream type (H.264 in a packetized stream)
+ 0xE0u | // Reserved bits (0b111)
+ 0x10u, // Elementary packet ID (13 bits)
+ 0x11u, //
+ 0xF0u | // Reserved bits (0b1111)
+ 0x00u | // Elementary stream info length unused bits
+ 0u, // Elementary stream info length (10 bits)
+ 10u, //
+ 0x28u, // AVC video descriptor tag
+ 4u, // Descriptor length
+ 0xA5u,
+ 0xF5u, 0xBDu, 0xBFu,
+ 0x2Au, // AVC timing and HRD descriptor tag
+ 2u, // Descriptor length
+ 0x7Eu, 0x1Fu,
+ // Elementary stream specific data:
+ 0x83u, // Stream type (lossless audio in a packetized stream)
+ 0xE0u | // Reserved bits (0b111)
+ 0x11u, // Elementary packet ID (13 bits)
+ 0x00u, //
+ 0xF0u | // Reserved bits (0b1111)
+ 0x00u | // Elementary stream info length unused bits
+ 0u, // Elementary stream info length (10 bits)
+ 4u, //
+ 0x83u, // LPCM audio stream descriptor tag
+ 2u, // Descriptor length
+ 0x26u,
+ 0x2Fu,
+ // Elementary stream specific data:
+ 0x0Fu, // Stream type (AAC in a packetized stream)
+ 0xE0u | // Reserved bits (0b111)
+ 0x11u, // Elementary packet ID (13 bits)
+ 0x01u, //
+ 0xF0u | // Reserved bits (0b1111)
+ 0x00u | // Elementary stream info length unused bits
+ 0u, // Elementary stream info length (10 bits)
+ 0u, //
+ // CRC:
+ 0x4Fu,
+ 0x63u, 0xABu, 0x6Eu};
+ static const uint8_t kStreamIds[] = {
+ WiFiDisplayElementaryStreamPacketizer::kFirstVideoStreamId,
+ WiFiDisplayElementaryStreamPacketizer::kPrivateStream1Id,
+ WiFiDisplayElementaryStreamPacketizer::kFirstAudioStreamId};
+
+ using ESDescriptor = WiFiDisplayElementaryStreamDescriptor;
+ std::vector<ESDescriptor> lpcm_descriptors;
+ lpcm_descriptors.emplace_back(ESDescriptor::LPCMAudioStream::Create(
+ ESDescriptor::LPCMAudioStream::SAMPLING_FREQUENCY_44_1K,
+ ESDescriptor::LPCMAudioStream::BITS_PER_SAMPLE_16, false,
+ ESDescriptor::LPCMAudioStream::NUMBER_OF_CHANNELS_STEREO));
+ std::vector<ESDescriptor> video_desciptors;
+ video_desciptors.emplace_back(ESDescriptor::AVCVideo::Create(
+ 0xA5u, true, true, true, 0x15u, 0xBDu, true));
+ video_desciptors.emplace_back(ESDescriptor::AVCTimingAndHRD::Create());
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos;
+ stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::VIDEO_H264,
+ std::move(video_desciptors));
+ stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::AUDIO_LPCM,
+ std::move(lpcm_descriptors));
+ stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::AUDIO_AAC);
+ WiFiDisplayElementaryStreamPacketizer elementary_stream_packetizer;
+ FakeTransportStreamPacketizer packetizer(
+ base::TimeDelta::FromMilliseconds(200), std::move(stream_infos));
+
+ size_t packet_index = 0u;
+ for (unsigned stream_index = 0; stream_index < kStreamCount; ++stream_index) {
+ const uint8_t* unit_header_data = nullptr;
+ size_t unit_header_size = 0u;
+ if (stream_index > 0u) { // Audio stream.
+ if (unit_.size() >= kVideoOnlyUnitSize)
+ continue;
+ if (stream_index == 1u) { // LPCM
+ unit_header_data = reinterpret_cast<const uint8_t*>("\xA0\x06\x00\x09");
+ unit_header_size = 4u;
+ }
+ }
+ for (const bool random_access : kBoolValues) {
+ EXPECT_TRUE(packetizer.EncodeElementaryStreamUnit(
+ stream_index, unit_.data(), unit_.size(), random_access, pts_, dts_,
+ true));
+ auto normalized_pts = pts_;
+ auto normalized_dts = dts_;
+ packetizer.NormalizeUnitTimeStamps(&normalized_pts, &normalized_dts);
+ WiFiDisplayElementaryStreamPacket elementary_stream_packet =
+ elementary_stream_packetizer.EncodeElementaryStreamUnit(
+ kStreamIds[stream_index], unit_header_data, unit_header_size,
+ unit_.data(), unit_.size(), normalized_pts, normalized_dts);
+
+ const uint8_t* unit_data_pos = unit_.data();
+ for (const auto& packet : packetizer.FetchPackets()) {
+ switch (ParseTransportStreamPacketId(packet)) {
+ case widi::kProgramAssociationTablePacketId:
+ if (packet_index < 4u)
+ EXPECT_EQ(0u, packet_index);
+ CheckTransportStreamProgramAssociationTablePacket(packet);
+ break;
+ case widi::kProgramMapTablePacketId:
+ if (packet_index < 4u)
+ EXPECT_EQ(1u, packet_index);
+ CheckTransportStreamProgramMapTablePacket(
+ packet, PacketPart(kProgramMapTable));
+ break;
+ case widi::kProgramClockReferencePacketId:
+ if (packet_index < 4u)
+ EXPECT_EQ(2u, packet_index);
+ CheckTransportStreamProgramClockReferencePacket(packet);
+ break;
+ default:
+ if (packet_index < 4u)
+ EXPECT_EQ(3u, packet_index);
+ CheckTransportStreamElementaryStreamPacket(
+ packet, elementary_stream_packet, stream_index,
+ kPacketIds[stream_index], random_access, &unit_data_pos);
+ }
+ ++packet_index;
+ }
+ EXPECT_EQ(unit_.data() + unit_.size(), unit_data_pos);
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(
+ WiFiDisplayElementaryStreamUnitPacketizationTests,
+ WiFiDisplayElementaryStreamUnitPacketizationTest,
+ testing::Combine(testing::Values(123u, 180u, 0x10000u),
+ testing::Values(base::TimeDelta::Max(),
+ base::TimeDelta::FromMicroseconds(0)),
+ testing::Values(base::TimeDelta::Max(),
+ base::TimeDelta::FromMicroseconds(
+ 1000 * INT64_C(0x123456789) / 90))));
+
+TEST(WiFiDisplayTransportStreamPacketizationTest, EncodeToMediaDatagramPacket) {
+ const size_t kPacketHeaderSize = 12u;
+
+ // Create fake units.
+ const size_t kUnitCount = 12u;
+ const size_t kUnitSize =
+ WiFiDisplayTransportStreamPacket::kPacketSize - 4u - 12u;
+ std::vector<std::array<uint8_t, kUnitSize>> units(kUnitCount);
+ for (auto& unit : units)
+ unit.fill(static_cast<uint8_t>(&unit - units.data()));
+
+ // Create transport stream packets.
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos;
+ stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::VIDEO_H264);
+ FakeTransportStreamPacketizer transport_stream_packetizer(
+ base::TimeDelta::FromMilliseconds(0), std::move(stream_infos));
+ for (const auto& unit : units) {
+ EXPECT_TRUE(transport_stream_packetizer.EncodeElementaryStreamUnit(
+ 0u, unit.data(), unit.size(), false, base::TimeTicks(),
+ base::TimeTicks(), &unit == &units.back()));
+ }
+ auto transport_stream_packets = transport_stream_packetizer.FetchPackets();
+ // There should be exactly one transport stream payload packet for each unit.
+ // There should also be some but not too many transport stream meta
+ // information packets.
+ EXPECT_EQ(1u, transport_stream_packets.size() / kUnitCount);
+
+ // Encode transport stream packets to datagram packets.
+ FakeMediaPacketizer packetizer(
+ base::TimeDelta::FromMilliseconds(0),
+ std::vector<WiFiDisplayElementaryStreamInfo>());
+ for (const auto& transport_stream_packet : transport_stream_packets) {
+ EXPECT_TRUE(packetizer.EncodeTransportStreamPacket(
+ transport_stream_packet,
+ &transport_stream_packet == &transport_stream_packets.back()));
+ }
+ auto packets = packetizer.FetchPackets();
+
+ // Check datagram packets.
+ ProgramClockReference pcr = {ProgramClockReference::kInvalidBase, 0u};
+ uint16_t sequence_number;
+ uint32_t synchronization_source_identifier;
+ auto transport_stream_packet_it = transport_stream_packets.cbegin();
+ for (const auto& packet : packets) {
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.data()),
+ std::min(kPacketHeaderSize, packet.size()));
+
+ // Packet flags.
+ uint16_t parsed_u16;
+ EXPECT_TRUE(header_reader.ReadU16(&parsed_u16));
+ EXPECT_EQ(rtp::kVersion2, parsed_u16 & rtp::kVersionMask);
+ EXPECT_FALSE(parsed_u16 & rtp::kPaddingFlag);
+ EXPECT_FALSE(parsed_u16 & rtp::kExtensionFlag);
+ EXPECT_EQ(0u, parsed_u16 & rtp::kContributingSourceCountMask);
+ EXPECT_FALSE(parsed_u16 & rtp::kMarkerFlag);
+ EXPECT_EQ(rtp::kPayloadTypeMP2T, parsed_u16 & rtp::kPayloadTypeMask);
+
+ // Packet sequence number.
+ uint16_t parsed_sequence_number;
+ EXPECT_TRUE(header_reader.ReadU16(&parsed_sequence_number));
+ if (&packet == &packets.front())
+ sequence_number = parsed_sequence_number;
+ EXPECT_EQ(sequence_number++, parsed_sequence_number);
+
+ // Packet time stamp.
+ uint32_t parsed_time_stamp;
+ EXPECT_TRUE(header_reader.ReadU32(&parsed_time_stamp));
+ if (pcr.base == ProgramClockReference::kInvalidBase) {
+ // This happens only for the first datagram packet.
+ EXPECT_TRUE(&packet == &packets.front());
+ // Ensure that the next datagram packet reaches the else branch.
+ EXPECT_FALSE(&packet == &packets.back());
+ } else {
+ // Check that
+ // 0 <= parsed_time_stamp - pcr.base <= kTimeStampSecond
+ // but allow pcr.base and parsed_time_stamp to wrap around in 32 bits.
+ EXPECT_LE((parsed_time_stamp - pcr.base) & 0xFFFFFFFFu,
+ ts::kTimeStampSecond)
+ << " Time stamp must not be smaller than PCR!";
+ }
+
+ // Packet synchronization source identifier.
+ uint32_t parsed_synchronization_source_identifier;
+ EXPECT_TRUE(
+ header_reader.ReadU32(&parsed_synchronization_source_identifier));
+ if (&packet == &packets.front()) {
+ synchronization_source_identifier =
+ parsed_synchronization_source_identifier;
+ }
+ EXPECT_EQ(synchronization_source_identifier,
+ parsed_synchronization_source_identifier);
+
+ EXPECT_EQ(0, header_reader.remaining());
+
+ // Packet payload.
+ size_t offset = kPacketHeaderSize;
+ while (offset + WiFiDisplayTransportStreamPacket::kPacketSize <=
+ packet.size() &&
+ transport_stream_packet_it != transport_stream_packets.end()) {
+ const auto& transport_stream_packet = *transport_stream_packet_it++;
+ const PacketPart parsed_transport_stream_packet_header(
+ packet.data() + offset, transport_stream_packet.header().size());
+ const PacketPart parsed_transport_stream_packet_payload(
+ parsed_transport_stream_packet_header.end(),
+ transport_stream_packet.payload().size());
+ const PacketPart parsed_transport_stream_packet_filler(
+ parsed_transport_stream_packet_payload.end(),
+ transport_stream_packet.filler().size());
+ offset += WiFiDisplayTransportStreamPacket::kPacketSize;
+
+ // Check bytes.
+ EXPECT_EQ(transport_stream_packet.header(),
+ parsed_transport_stream_packet_header);
+ EXPECT_EQ(transport_stream_packet.payload(),
+ parsed_transport_stream_packet_payload);
+ EXPECT_EQ(transport_stream_packet.filler().size(),
+ std::count(parsed_transport_stream_packet_filler.begin(),
+ parsed_transport_stream_packet_filler.end(),
+ transport_stream_packet.filler().value()));
+
+ if (ParseTransportStreamPacketId(transport_stream_packet) ==
+ widi::kProgramClockReferencePacketId) {
+ pcr = ParseProgramClockReference(
+ &transport_stream_packet.header().begin()[6]);
+ }
+ }
+ EXPECT_EQ(offset, packet.size()) << " Extra packet payload bytes.";
+
+ // Check that the payload contains a correct number of transport stream
+ // packets.
+ const size_t transport_stream_packet_count_in_datagram_packet =
+ packet.size() / WiFiDisplayTransportStreamPacket::kPacketSize;
+ if (&packet == &packets.back()) {
+ EXPECT_GE(transport_stream_packet_count_in_datagram_packet, 1u);
+ EXPECT_LE(transport_stream_packet_count_in_datagram_packet,
+ widi::kMaxTransportStreamPacketCountPerDatagramPacket);
+ } else {
+ EXPECT_EQ(widi::kMaxTransportStreamPacketCountPerDatagramPacket,
+ transport_stream_packet_count_in_datagram_packet);
+ }
+ }
+ EXPECT_EQ(transport_stream_packets.end(), transport_stream_packet_it);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.cc
new file mode 100644
index 00000000000..f1bfe4cbea7
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.cc
@@ -0,0 +1,225 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_session.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/timer/timer.h"
+#include "content/public/common/service_registry.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h"
+#include "third_party/wds/src/libwds/public/logging.h"
+#include "third_party/wds/src/libwds/public/media_manager.h"
+
+namespace {
+const char kErrorInternal[] = "An internal error has occurred";
+const char kErrorTimeout[] = "Sink became unresponsive";
+
+static void LogWDSError(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ char buffer[256];
+ vsnprintf(buffer, 256, format, args);
+ va_end(args);
+ DVLOG(1) << "[WDS] " << buffer;
+}
+
+} // namespace
+
+namespace extensions {
+
+using api::display_source::ErrorType;
+
+WiFiDisplaySession::WiFiDisplaySession(
+ const DisplaySourceSessionParams& params)
+ : binding_(this),
+ params_(params),
+ cseq_(0),
+ timer_id_(0),
+ weak_factory_(this) {
+ DCHECK(params_.render_frame);
+ wds::LogSystem::set_error_func(&LogWDSError);
+ params.render_frame->GetServiceRegistry()->ConnectToRemoteService(
+ mojo::GetProxy(&service_));
+ service_.set_connection_error_handler(base::Bind(
+ &WiFiDisplaySession::OnIPCConnectionError,
+ weak_factory_.GetWeakPtr()));
+
+ service_->SetClient(binding_.CreateInterfacePtrAndBind());
+ binding_.set_connection_error_handler(base::Bind(
+ &WiFiDisplaySession::OnIPCConnectionError,
+ weak_factory_.GetWeakPtr()));
+}
+
+WiFiDisplaySession::~WiFiDisplaySession() {
+}
+
+void WiFiDisplaySession::Start(const CompletionCallback& callback) {
+ DCHECK_EQ(DisplaySourceSession::Idle, state_);
+ DCHECK(!terminated_callback_.is_null())
+ << "Should be set with 'SetNotificationCallbacks'";
+ DCHECK(!error_callback_.is_null())
+ << "Should be set with 'SetNotificationCallbacks'";
+
+ service_->Connect(params_.sink_id, params_.auth_method, params_.auth_data);
+ state_ = DisplaySourceSession::Establishing;
+ start_completion_callback_ = callback;
+}
+
+void WiFiDisplaySession::Terminate(const CompletionCallback& callback) {
+ DCHECK_EQ(DisplaySourceSession::Established, state_);
+ Terminate();
+ teminate_completion_callback_ = callback;
+}
+
+void WiFiDisplaySession::OnConnected(const mojo::String& ip_address) {
+ DCHECK_EQ(DisplaySourceSession::Established, state_);
+ ip_address_ = ip_address;
+ media_manager_.reset(
+ new WiFiDisplayMediaManager(
+ params_.video_track,
+ params_.audio_track,
+ base::Bind(
+ &WiFiDisplaySession::OnMediaError,
+ weak_factory_.GetWeakPtr())));
+ wfd_source_.reset(wds::Source::Create(this, media_manager_.get(), this));
+ wfd_source_->Start();
+}
+
+void WiFiDisplaySession::OnConnectRequestHandled(bool success,
+ const mojo::String& error) {
+ DCHECK_EQ(DisplaySourceSession::Establishing, state_);
+ state_ =
+ success ? DisplaySourceSession::Established : DisplaySourceSession::Idle;
+ RunStartCallback(success, error);
+}
+
+void WiFiDisplaySession::OnTerminated() {
+ DCHECK_NE(DisplaySourceSession::Idle, state_);
+ state_ = DisplaySourceSession::Idle;
+ media_manager_.reset();
+ wfd_source_.reset();
+ terminated_callback_.Run();
+}
+
+void WiFiDisplaySession::OnDisconnectRequestHandled(bool success,
+ const mojo::String& error) {
+ RunTerminateCallback(success, error);
+}
+
+void WiFiDisplaySession::OnError(int32_t type,
+ const mojo::String& description) {
+ DCHECK(type > api::display_source::ERROR_TYPE_NONE
+ && type <= api::display_source::ERROR_TYPE_LAST);
+ DCHECK_EQ(DisplaySourceSession::Established, state_);
+ error_callback_.Run(static_cast<ErrorType>(type), description);
+}
+
+void WiFiDisplaySession::OnMessage(const mojo::String& data) {
+ DCHECK_EQ(DisplaySourceSession::Established, state_);
+ DCHECK(wfd_source_);
+ wfd_source_->RTSPDataReceived(data);
+}
+
+std::string WiFiDisplaySession::GetLocalIPAddress() const {
+ return ip_address_;
+}
+
+int WiFiDisplaySession::GetNextCSeq(int* initial_peer_cseq) const {
+ return ++cseq_;
+}
+
+void WiFiDisplaySession::SendRTSPData(const std::string& message) {
+ service_->SendMessage(message);
+}
+
+unsigned WiFiDisplaySession::CreateTimer(int seconds) {
+ scoped_ptr<base::Timer> timer(new base::Timer(true, true));
+ auto insert_ret = timers_.insert(
+ std::pair<int, scoped_ptr<base::Timer>>(
+ ++timer_id_, std::move(timer)));
+ DCHECK(insert_ret.second);
+ insert_ret.first->second->Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(seconds),
+ base::Bind(&wds::Source::OnTimerEvent,
+ base::Unretained(wfd_source_.get()),
+ timer_id_));
+ return static_cast<unsigned>(timer_id_);
+}
+
+void WiFiDisplaySession::ReleaseTimer(unsigned timer_id) {
+ auto it = timers_.find(static_cast<int>(timer_id));
+ if (it != timers_.end())
+ timers_.erase(it);
+}
+
+void WiFiDisplaySession::ErrorOccurred(wds::ErrorType error) {
+ DCHECK_NE(DisplaySourceSession::Idle, state_);
+ if (error == wds::TimeoutError) {
+ error_callback_.Run(api::display_source::ERROR_TYPE_TIMEOUT_ERROR,
+ kErrorTimeout);
+ } else {
+ error_callback_.Run(api::display_source::ERROR_TYPE_UNKNOWN_ERROR,
+ kErrorInternal);
+ }
+ // The session cannot continue.
+ Terminate();
+}
+
+void WiFiDisplaySession::SessionCompleted() {
+ DCHECK_NE(DisplaySourceSession::Idle, state_);
+ // The session has finished normally.
+ Terminate();
+}
+
+void WiFiDisplaySession::OnIPCConnectionError() {
+ // We must explicitly notify the session termination as it will never
+ // arrive from browser process (IPC is broken).
+ switch (state_) {
+ case DisplaySourceSession::Idle:
+ case DisplaySourceSession::Establishing:
+ RunStartCallback(false, kErrorInternal);
+ break;
+ case DisplaySourceSession::Terminating:
+ case DisplaySourceSession::Established:
+ error_callback_.Run(api::display_source::ERROR_TYPE_UNKNOWN_ERROR,
+ kErrorInternal);
+ state_ = DisplaySourceSession::Idle;
+ terminated_callback_.Run();
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void WiFiDisplaySession::OnMediaError(const std::string& error) {
+ DCHECK_NE(DisplaySourceSession::Idle, state_);
+ error_callback_.Run(api::display_source::ERROR_TYPE_MEDIA_PIPELINE_ERROR,
+ error);
+ Terminate();
+}
+
+void WiFiDisplaySession::Terminate() {
+ if (state_ == DisplaySourceSession::Established) {
+ service_->Disconnect();
+ state_ = DisplaySourceSession::Terminating;
+ }
+}
+
+void WiFiDisplaySession::RunStartCallback(bool success,
+ const std::string& error_message) {
+ if (!start_completion_callback_.is_null())
+ start_completion_callback_.Run(success, error_message);
+}
+
+void WiFiDisplaySession::RunTerminateCallback(
+ bool success,
+ const std::string& error_message) {
+ if (!teminate_completion_callback_.is_null())
+ teminate_completion_callback_.Run(success, error_message);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.h
new file mode 100644
index 00000000000..bacb875dbac
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.h
@@ -0,0 +1,95 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_SESSION_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_SESSION_H_
+
+#include <map>
+#include <string>
+
+#include "extensions/common/mojo/wifi_display_session_service.mojom.h"
+#include "extensions/renderer/api/display_source/display_source_session.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "third_party/wds/src/libwds/public/source.h"
+
+namespace base {
+class Timer;
+} // namespace base
+
+namespace extensions {
+
+class WiFiDisplayMediaManager;
+
+// This class represents a single Wi-Fi Display session.
+// It manages life-cycle of the session and it is also responsible for
+// exchange of session controlling (RTSP) messages with the sink.
+class WiFiDisplaySession: public DisplaySourceSession,
+ public WiFiDisplaySessionServiceClient,
+ public wds::Peer::Delegate,
+ public wds::Peer::Observer {
+ public:
+ explicit WiFiDisplaySession(
+ const DisplaySourceSessionParams& params);
+ ~WiFiDisplaySession() override;
+
+ private:
+ using DisplaySourceSession::CompletionCallback;
+ // DisplaySourceSession overrides.
+ void Start(const CompletionCallback& callback) override;
+ void Terminate(const CompletionCallback& callback) override;
+
+ // WiFiDisplaySessionServiceClient overrides.
+ void OnConnected(const mojo::String& ip_address) override;
+ void OnConnectRequestHandled(bool success,
+ const mojo::String& error) override;
+ void OnTerminated() override;
+ void OnDisconnectRequestHandled(bool success,
+ const mojo::String& error) override;
+ void OnError(int32_t type, const mojo::String& description) override;
+ void OnMessage(const mojo::String& data) override;
+
+ // wds::Peer::Delegate overrides.
+ unsigned CreateTimer(int seconds) override;
+ void ReleaseTimer(unsigned timer_id) override;
+ void SendRTSPData(const std::string& message) override;
+ std::string GetLocalIPAddress() const override;
+ int GetNextCSeq(int* initial_peer_cseq = nullptr) const override;
+
+ // wds::Peer::Observer overrides.
+ void ErrorOccurred(wds::ErrorType error) override;
+ void SessionCompleted() override;
+
+ // A connection error handler for the mojo objects used in this class.
+ void OnIPCConnectionError();
+
+ // An error handler for media pipeline error.
+ void OnMediaError(const std::string& error);
+
+ void Terminate();
+
+ void RunStartCallback(bool success, const std::string& error = "");
+ void RunTerminateCallback(bool success, const std::string& error = "");
+
+ private:
+ scoped_ptr<wds::Source> wfd_source_;
+ scoped_ptr<WiFiDisplayMediaManager> media_manager_;
+ WiFiDisplaySessionServicePtr service_;
+ mojo::Binding<WiFiDisplaySessionServiceClient> binding_;
+ std::string ip_address_;
+ std::map<int, scoped_ptr<base::Timer>> timers_;
+
+ DisplaySourceSessionParams params_;
+ CompletionCallback start_completion_callback_;
+ CompletionCallback teminate_completion_callback_;
+ // Holds sequence number for the following RTSP request-response pair.
+ mutable int cseq_;
+ int timer_id_;
+ base::WeakPtrFactory<WiFiDisplaySession> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplaySession);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_SESSION_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h
new file mode 100644
index 00000000000..152b4a7ea23
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h
@@ -0,0 +1,43 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_STREAM_PACKET_PART_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_STREAM_PACKET_PART_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+
+namespace extensions {
+
+// WiFi Display elementary stream unit packetization consists of multiple
+// packetization phases. During those phases, unit buffer bytes are not
+// modified but only wrapped in packets.
+// This class allows different kind of WiFi Display stream packets to refer to
+// unit buffer bytes without copying them.
+class WiFiDisplayStreamPacketPart {
+ public:
+ WiFiDisplayStreamPacketPart(const uint8_t* data, size_t size)
+ : data_(data), size_(size) {}
+ template <size_t N>
+ explicit WiFiDisplayStreamPacketPart(const uint8_t (&data)[N])
+ : WiFiDisplayStreamPacketPart(data, N) {}
+
+ const uint8_t* begin() const { return data(); }
+ const uint8_t* data() const { return data_; }
+ bool empty() const { return size() == 0u; }
+ const uint8_t* end() const { return data() + size(); }
+ size_t size() const { return size_; }
+
+ private:
+ const uint8_t* const data_;
+ const size_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplayStreamPacketPart);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_STREAM_PACKET_BASE_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc
new file mode 100644
index 00000000000..eee0a52c55a
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc
@@ -0,0 +1,727 @@
+// Copyright 2016 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.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h"
+
+#include <algorithm>
+#include <cstring>
+#include <utility>
+
+#include "base/logging.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h"
+
+namespace extensions {
+namespace {
+
+uint32_t crc32(uint32_t crc, const uint8_t* data, size_t len) {
+ static const uint32_t table[256] = {
+ 0x00000000, 0xb71dc104, 0x6e3b8209, 0xd926430d, 0xdc760413, 0x6b6bc517,
+ 0xb24d861a, 0x0550471e, 0xb8ed0826, 0x0ff0c922, 0xd6d68a2f, 0x61cb4b2b,
+ 0x649b0c35, 0xd386cd31, 0x0aa08e3c, 0xbdbd4f38, 0x70db114c, 0xc7c6d048,
+ 0x1ee09345, 0xa9fd5241, 0xacad155f, 0x1bb0d45b, 0xc2969756, 0x758b5652,
+ 0xc836196a, 0x7f2bd86e, 0xa60d9b63, 0x11105a67, 0x14401d79, 0xa35ddc7d,
+ 0x7a7b9f70, 0xcd665e74, 0xe0b62398, 0x57abe29c, 0x8e8da191, 0x39906095,
+ 0x3cc0278b, 0x8bdde68f, 0x52fba582, 0xe5e66486, 0x585b2bbe, 0xef46eaba,
+ 0x3660a9b7, 0x817d68b3, 0x842d2fad, 0x3330eea9, 0xea16ada4, 0x5d0b6ca0,
+ 0x906d32d4, 0x2770f3d0, 0xfe56b0dd, 0x494b71d9, 0x4c1b36c7, 0xfb06f7c3,
+ 0x2220b4ce, 0x953d75ca, 0x28803af2, 0x9f9dfbf6, 0x46bbb8fb, 0xf1a679ff,
+ 0xf4f63ee1, 0x43ebffe5, 0x9acdbce8, 0x2dd07dec, 0x77708634, 0xc06d4730,
+ 0x194b043d, 0xae56c539, 0xab068227, 0x1c1b4323, 0xc53d002e, 0x7220c12a,
+ 0xcf9d8e12, 0x78804f16, 0xa1a60c1b, 0x16bbcd1f, 0x13eb8a01, 0xa4f64b05,
+ 0x7dd00808, 0xcacdc90c, 0x07ab9778, 0xb0b6567c, 0x69901571, 0xde8dd475,
+ 0xdbdd936b, 0x6cc0526f, 0xb5e61162, 0x02fbd066, 0xbf469f5e, 0x085b5e5a,
+ 0xd17d1d57, 0x6660dc53, 0x63309b4d, 0xd42d5a49, 0x0d0b1944, 0xba16d840,
+ 0x97c6a5ac, 0x20db64a8, 0xf9fd27a5, 0x4ee0e6a1, 0x4bb0a1bf, 0xfcad60bb,
+ 0x258b23b6, 0x9296e2b2, 0x2f2bad8a, 0x98366c8e, 0x41102f83, 0xf60dee87,
+ 0xf35da999, 0x4440689d, 0x9d662b90, 0x2a7bea94, 0xe71db4e0, 0x500075e4,
+ 0x892636e9, 0x3e3bf7ed, 0x3b6bb0f3, 0x8c7671f7, 0x555032fa, 0xe24df3fe,
+ 0x5ff0bcc6, 0xe8ed7dc2, 0x31cb3ecf, 0x86d6ffcb, 0x8386b8d5, 0x349b79d1,
+ 0xedbd3adc, 0x5aa0fbd8, 0xeee00c69, 0x59fdcd6d, 0x80db8e60, 0x37c64f64,
+ 0x3296087a, 0x858bc97e, 0x5cad8a73, 0xebb04b77, 0x560d044f, 0xe110c54b,
+ 0x38368646, 0x8f2b4742, 0x8a7b005c, 0x3d66c158, 0xe4408255, 0x535d4351,
+ 0x9e3b1d25, 0x2926dc21, 0xf0009f2c, 0x471d5e28, 0x424d1936, 0xf550d832,
+ 0x2c769b3f, 0x9b6b5a3b, 0x26d61503, 0x91cbd407, 0x48ed970a, 0xfff0560e,
+ 0xfaa01110, 0x4dbdd014, 0x949b9319, 0x2386521d, 0x0e562ff1, 0xb94beef5,
+ 0x606dadf8, 0xd7706cfc, 0xd2202be2, 0x653deae6, 0xbc1ba9eb, 0x0b0668ef,
+ 0xb6bb27d7, 0x01a6e6d3, 0xd880a5de, 0x6f9d64da, 0x6acd23c4, 0xddd0e2c0,
+ 0x04f6a1cd, 0xb3eb60c9, 0x7e8d3ebd, 0xc990ffb9, 0x10b6bcb4, 0xa7ab7db0,
+ 0xa2fb3aae, 0x15e6fbaa, 0xccc0b8a7, 0x7bdd79a3, 0xc660369b, 0x717df79f,
+ 0xa85bb492, 0x1f467596, 0x1a163288, 0xad0bf38c, 0x742db081, 0xc3307185,
+ 0x99908a5d, 0x2e8d4b59, 0xf7ab0854, 0x40b6c950, 0x45e68e4e, 0xf2fb4f4a,
+ 0x2bdd0c47, 0x9cc0cd43, 0x217d827b, 0x9660437f, 0x4f460072, 0xf85bc176,
+ 0xfd0b8668, 0x4a16476c, 0x93300461, 0x242dc565, 0xe94b9b11, 0x5e565a15,
+ 0x87701918, 0x306dd81c, 0x353d9f02, 0x82205e06, 0x5b061d0b, 0xec1bdc0f,
+ 0x51a69337, 0xe6bb5233, 0x3f9d113e, 0x8880d03a, 0x8dd09724, 0x3acd5620,
+ 0xe3eb152d, 0x54f6d429, 0x7926a9c5, 0xce3b68c1, 0x171d2bcc, 0xa000eac8,
+ 0xa550add6, 0x124d6cd2, 0xcb6b2fdf, 0x7c76eedb, 0xc1cba1e3, 0x76d660e7,
+ 0xaff023ea, 0x18ede2ee, 0x1dbda5f0, 0xaaa064f4, 0x738627f9, 0xc49be6fd,
+ 0x09fdb889, 0xbee0798d, 0x67c63a80, 0xd0dbfb84, 0xd58bbc9a, 0x62967d9e,
+ 0xbbb03e93, 0x0cadff97, 0xb110b0af, 0x060d71ab, 0xdf2b32a6, 0x6836f3a2,
+ 0x6d66b4bc, 0xda7b75b8, 0x035d36b5, 0xb440f7b1};
+ for (; len; ++data, --len)
+ crc = (crc >> 8) ^ table[(crc & 0xFFu) ^ *data];
+ return crc;
+}
+
+// Code and parameters related to the Program Specific Information (PSI)
+// specification.
+namespace psi {
+
+const uint8_t kProgramAssociationTableId = 0x00u;
+const uint8_t kProgramMapTableId = 0x02u;
+
+const uint16_t kFirstProgramNumber = 0x0001u;
+
+const size_t kCrcSize = 4u;
+const size_t kProgramMapTableElementaryStreamEntryBaseSize = 5u;
+const size_t kTableHeaderSize = 3u;
+
+size_t FillInTablePointer(uint8_t* dst, size_t min_size) {
+ size_t i = 1u;
+ if (i < min_size) {
+ std::memset(&dst[i], 0xFF, min_size - i); // Pointer filler bytes
+ i = min_size;
+ }
+ dst[0] = i - 1u; // Pointer field
+ return i;
+}
+
+size_t FillInTableHeaderAndCrc(uint8_t* header_dst,
+ uint8_t* crc_dst,
+ uint8_t table_id) {
+ size_t i;
+ const uint8_t* const header_end = header_dst + kTableHeaderSize;
+ const uint8_t* const crc_end = crc_dst + kCrcSize;
+ const size_t section_length = static_cast<size_t>(crc_end - header_end);
+ DCHECK_LE(section_length, 1021u);
+
+ // Table header.
+ i = 0u;
+ header_dst[i++] = table_id;
+ header_dst[i++] =
+ (0x1u << 7) | // Section syntax indicator (1 for PAT and PMT)
+ (0x0u << 6) | // Private bit (0 for PAT and PMT)
+ (0x3u << 4) | // Reserved bits (both bits on)
+ (0x0u << 2) | // Section length unused bits (both bits off)
+ ((section_length >> 8) & 0x03u); // Section length (10 bits)
+ header_dst[i++] = section_length & 0xFFu; //
+ DCHECK_EQ(kTableHeaderSize, i);
+
+ // CRC.
+ uint32_t crc =
+ crc32(0xFFFFFFFFu, header_dst, static_cast<size_t>(crc_dst - header_dst));
+ i = 0u;
+ // Avoid swapping the crc by reversing write order.
+ crc_dst[i++] = crc & 0xFFu;
+ crc_dst[i++] = (crc >> 8) & 0xFFu;
+ crc_dst[i++] = (crc >> 16) & 0xFFu;
+ crc_dst[i++] = (crc >> 24) & 0xFFu;
+ DCHECK_EQ(kCrcSize, i);
+ return i;
+}
+
+size_t FillInTableSyntax(uint8_t* dst,
+ uint16_t table_id_extension,
+ uint8_t version_number) {
+ size_t i = 0u;
+ dst[i++] = table_id_extension >> 8;
+ dst[i++] = table_id_extension & 0xFFu;
+ dst[i++] = (0x3u << 6) | // Reserved bits (both bits on)
+ ((version_number & 0x1Fu) << 1) | // Version number (5 bits)
+ (0x1u << 0); // Current indicator
+ dst[i++] = 0u; // Section number
+ dst[i++] = 0u; // Last section number
+ return i;
+}
+
+size_t FillInProgramAssociationTableEntry(uint8_t* dst,
+ uint16_t program_number,
+ unsigned pmt_packet_id) {
+ size_t i = 0u;
+ dst[i++] = program_number >> 8;
+ dst[i++] = program_number & 0xFFu;
+ dst[i++] = (0x7u << 5) | // Reserved bits (all 3 bits on)
+ ((pmt_packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits)
+ dst[i++] = pmt_packet_id & 0xFFu; //
+ return i;
+}
+
+size_t FillInProgramMapTableData(uint8_t* dst, unsigned pcr_packet_id) {
+ size_t i = 0u;
+ dst[i++] = (0x7u << 5) | // Reserved bits (all 3 bits on)
+ ((pcr_packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits)
+ dst[i++] = pcr_packet_id & 0xFFu; //
+ dst[i++] = (0xFu << 4) | // Reserved bits (all 4 bits on)
+ (0x0u << 2) | // Program info length unused bits (both bits off)
+ ((0u >> 8) & 0x3u); // Program info length (10 bits)
+ dst[i++] = 0u & 0xFFu; //
+ // No program descriptors
+ return i;
+}
+
+size_t CalculateElementaryStreamInfoLength(
+ const std::vector<WiFiDisplayElementaryStreamDescriptor>& es_descriptors) {
+ size_t es_info_length = 0u;
+ for (const auto& es_descriptor : es_descriptors)
+ es_info_length += es_descriptor.size();
+ DCHECK_EQ(0u, es_info_length >> 8);
+ return es_info_length;
+}
+
+size_t FillInProgramMapTableElementaryStreamEntry(
+ uint8_t* dst,
+ uint8_t stream_type,
+ unsigned es_packet_id,
+ size_t es_info_length,
+ const std::vector<WiFiDisplayElementaryStreamDescriptor>& es_descriptors) {
+ DCHECK_EQ(CalculateElementaryStreamInfoLength(es_descriptors),
+ es_info_length);
+ DCHECK_EQ(0u, es_info_length >> 10);
+ size_t i = 0u;
+ dst[i++] = stream_type;
+ dst[i++] = (0x7u << 5) | // Reserved bits (all 3 bits on)
+ ((es_packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits)
+ dst[i++] = es_packet_id & 0xFFu; //
+ dst[i++] = (0xFu << 4) | // Reserved bits (all 4 bits on)
+ (0x0u << 2) | // ES info length unused bits (both bits off)
+ ((es_info_length >> 8) & 0x3u); // ES info length (10 bits)
+ dst[i++] = es_info_length & 0xFFu; //
+ for (const auto& es_descriptor : es_descriptors) {
+ std::memcpy(&dst[i], es_descriptor.data(), es_descriptor.size());
+ i += es_descriptor.size();
+ }
+ return i;
+}
+
+} // namespace psi
+
+// Code and parameters related to the MPEG Transport Stream (MPEG-TS)
+// specification.
+namespace ts {
+
+const size_t kAdaptationFieldLengthSize = 1u;
+const size_t kAdaptationFieldFlagsSize = 1u;
+const size_t kPacketHeaderSize = 4u;
+const size_t kProgramClockReferenceSize = 6u;
+
+size_t FillInProgramClockReference(uint8_t* dst, const base::TimeTicks& pcr) {
+ // Convert to the number of 27 MHz ticks since some epoch.
+ const uint64_t us =
+ static_cast<uint64_t>((pcr - base::TimeTicks()).InMicroseconds());
+ const uint64_t n = 27u * us;
+ const uint64_t base = n / 300u; // 90 kHz
+ const uint64_t extension = n % 300u;
+
+ size_t i = 0u;
+ dst[i++] = (base >> 25) & 0xFFu; // Base (33 bits)
+ dst[i++] = (base >> 17) & 0xFFu; //
+ dst[i++] = (base >> 9) & 0xFFu; //
+ dst[i++] = (base >> 1) & 0xFFu; //
+ dst[i++] = ((base & 0x01u) << 7) | //
+ (0x3Fu << 1) | // Reserved bits (all 6 bits on)
+ ((extension >> 8) & 0x1u); // Extension (9 bits)
+ dst[i++] = extension & 0xFFu; //
+ DCHECK_EQ(kProgramClockReferenceSize, i);
+ return i;
+}
+
+size_t FillInAdaptationFieldLengthFromSize(uint8_t* dst, size_t size) {
+ size_t i = 0u;
+ dst[i++] = size - 1u;
+ DCHECK_EQ(kAdaptationFieldLengthSize, i);
+ return i;
+}
+
+size_t FillInAdaptationFieldFlags(uint8_t* dst,
+ bool random_access_indicator,
+ const base::TimeTicks& pcr) {
+ size_t i = 0u;
+ dst[i++] = (0x0u << 7) | // Discontinuity indicator
+ (random_access_indicator << 6) | // Random access indicator
+ (0x0u << 5) | // Elementary stream priority indicator
+ ((!pcr.is_null()) << 4) | // PCR flag
+ (0x0u << 3) | // OPCR flag
+ (0x0u << 2) | // Splicing point flag
+ (0x0u << 1) | // Transport private data flag
+ (0x0u << 0); // Adaptation field extension flag
+ DCHECK_EQ(kAdaptationFieldFlagsSize, i);
+ return i;
+}
+
+size_t FillInAdaptationField(uint8_t* dst,
+ bool random_access_indicator,
+ size_t min_size) {
+ // Reserve space for a length.
+ size_t i = kAdaptationFieldLengthSize;
+
+ if (random_access_indicator || i < min_size) {
+ const base::TimeTicks pcr;
+ i += FillInAdaptationFieldFlags(&dst[i], random_access_indicator, pcr);
+
+ if (i < min_size) {
+ std::memset(&dst[i], 0xFF, min_size - i); // Stuffing bytes.
+ i = min_size;
+ }
+ }
+
+ // Fill in a length now that the size is known.
+ FillInAdaptationFieldLengthFromSize(dst, i);
+
+ return i;
+}
+
+size_t FillInPacketHeader(uint8_t* dst,
+ bool payload_unit_start_indicator,
+ unsigned packet_id,
+ bool adaptation_field_flag,
+ unsigned continuity_counter) {
+ size_t i = 0u;
+ dst[i++] = 0x47; // Sync byte ('G')
+ dst[i++] =
+ (0x0u << 7) | // Transport error indicator
+ (payload_unit_start_indicator << 6) | // Payload unit start indicator
+ (0x0 << 5) | // Transport priority
+ ((packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits)
+ dst[i++] = packet_id & 0xFFu; //
+ dst[i++] = (0x0u << 6) | // Scrambling control (0b00 for not)
+ (adaptation_field_flag << 5) | // Adaptation field flag
+ (0x1u << 4) | // Payload flag
+ (continuity_counter & 0xFu); // Continuity counter
+ DCHECK_EQ(kPacketHeaderSize, i);
+ return i;
+}
+
+} // namespace ts
+
+// Code and parameters related to the WiFi Display specification.
+namespace widi {
+
+const size_t kUnitHeaderMaxSize = 4u;
+
+// Maximum interval between meta information which includes:
+// * Program Association Table (PAT)
+// * Program Map Table (PMT)
+// * Program Clock Reference (PCR)
+const int kMaxMillisecondsBetweenMetaInformation = 100u;
+
+const unsigned kProgramAssociationTablePacketId = 0x0000u;
+const unsigned kProgramMapTablePacketId = 0x0100u;
+const unsigned kProgramClockReferencePacketId = 0x1000u;
+const unsigned kVideoStreamPacketId = 0x1011u;
+const unsigned kFirstAudioStreamPacketId = 0x1100u;
+
+size_t FillInUnitHeader(uint8_t* dst,
+ const WiFiDisplayElementaryStreamInfo& stream_info) {
+ size_t i = 0u;
+
+ if (stream_info.type() == WiFiDisplayElementaryStreamInfo::AUDIO_LPCM) {
+ // Convert an LPCM audio stream descriptor to an LPCM unit header.
+ if (const auto* lpcm_descriptor =
+ stream_info.FindDescriptor<
+ WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream>()) {
+ dst[i++] = 0xA0u; // Sub stream ID (0th sub stream)
+ dst[i++] = WiFiDisplayTransportStreamPacketizer::LPCM::kFramesPerUnit;
+ dst[i++] = ((0x00u << 1) | // Reserved (all 7 bits off)
+ (lpcm_descriptor->emphasis_flag() << 0));
+ dst[i++] = ((lpcm_descriptor->bits_per_sample() << 6) |
+ (lpcm_descriptor->sampling_frequency() << 3) |
+ (lpcm_descriptor->number_of_channels() << 0));
+ }
+ }
+
+ DCHECK_LE(i, kUnitHeaderMaxSize);
+ return i;
+}
+
+} // namespace widi
+
+} // namespace
+
+WiFiDisplayTransportStreamPacket::WiFiDisplayTransportStreamPacket(
+ const uint8_t* header_data,
+ size_t header_size)
+ : header_(header_data, header_size),
+ payload_(header_.end(), 0u),
+ filler_(kPacketSize - header_size) {}
+
+WiFiDisplayTransportStreamPacket::WiFiDisplayTransportStreamPacket(
+ const uint8_t* header_data,
+ size_t header_size,
+ const uint8_t* payload_data)
+ : header_(header_data, header_size),
+ payload_(payload_data, kPacketSize - header_size),
+ filler_(0u) {}
+
+struct WiFiDisplayTransportStreamPacketizer::ElementaryStreamState {
+ ElementaryStreamState(WiFiDisplayElementaryStreamInfo info,
+ uint16_t packet_id,
+ uint8_t stream_id)
+ : info(std::move(info)),
+ info_length(
+ psi::CalculateElementaryStreamInfoLength(this->info.descriptors())),
+ packet_id(packet_id),
+ stream_id(stream_id) {}
+
+ WiFiDisplayElementaryStreamInfo info;
+ uint8_t info_length;
+ struct {
+ uint8_t continuity = 0u;
+ } counters;
+ uint16_t packet_id;
+ uint8_t stream_id;
+};
+
+WiFiDisplayTransportStreamPacketizer::WiFiDisplayTransportStreamPacketizer(
+ const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos)
+ : delay_for_unit_time_stamps_(delay_for_unit_time_stamps) {
+ std::memset(&counters_, 0x00, sizeof(counters_));
+ if (!stream_infos.empty())
+ CHECK(SetElementaryStreams(std::move(stream_infos)));
+}
+
+WiFiDisplayTransportStreamPacketizer::~WiFiDisplayTransportStreamPacketizer() {}
+
+bool WiFiDisplayTransportStreamPacketizer::EncodeElementaryStreamUnit(
+ unsigned stream_index,
+ const uint8_t* unit_data,
+ size_t unit_size,
+ bool random_access,
+ base::TimeTicks pts,
+ base::TimeTicks dts,
+ bool flush) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_LT(stream_index, stream_states_.size());
+ ElementaryStreamState& stream_state = stream_states_[stream_index];
+
+ if (program_clock_reference_.is_null() ||
+ base::TimeTicks::Now() - program_clock_reference_ >
+ base::TimeDelta::FromMilliseconds(
+ widi::kMaxMillisecondsBetweenMetaInformation) /
+ 2) {
+ if (!EncodeMetaInformation(false))
+ return false;
+ }
+
+ uint8_t unit_header_data[widi::kUnitHeaderMaxSize];
+ const size_t unit_header_size =
+ widi::FillInUnitHeader(unit_header_data, stream_state.info);
+
+ UpdateDelayForUnitTimeStamps(pts, dts);
+ NormalizeUnitTimeStamps(&pts, &dts);
+
+ WiFiDisplayElementaryStreamPacketizer elementary_stream_packetizer;
+ WiFiDisplayElementaryStreamPacket elementary_stream_packet =
+ elementary_stream_packetizer.EncodeElementaryStreamUnit(
+ stream_state.stream_id, unit_header_data, unit_header_size, unit_data,
+ unit_size, pts, dts);
+
+ size_t adaptation_field_min_size = 0u;
+ uint8_t header_data[WiFiDisplayTransportStreamPacket::kPacketSize];
+ bool is_payload_unit_end;
+ bool is_payload_unit_start = true;
+ size_t remaining_unit_size = elementary_stream_packet.unit().size();
+ do {
+ // Fill in headers and an adaptation field:
+ // * Transport stream packet header
+ // * Transport stream adaptation field
+ // (only for the first and/or the last packet):
+ // - for the first packet to hold flags
+ // - for the last packet to hold padding
+ // * PES packet header (only for the first packet):
+ // - PES packet header base
+ // - Optional PES header base
+ // - Optional PES header optional fields:
+ // - Presentation time stamp
+ // - Decoding time stamp
+ bool adaptation_field_flag = false;
+ size_t header_min_size;
+ if (is_payload_unit_start || is_payload_unit_end) {
+ header_min_size = ts::kPacketHeaderSize;
+ if (is_payload_unit_start) {
+ header_min_size += elementary_stream_packet.header().size() +
+ elementary_stream_packet.unit_header().size();
+ }
+ adaptation_field_min_size =
+ std::max(
+ WiFiDisplayTransportStreamPacket::kPacketSize - header_min_size,
+ remaining_unit_size) -
+ remaining_unit_size;
+ adaptation_field_flag = adaptation_field_min_size > 0 ||
+ (is_payload_unit_start && random_access);
+ }
+ size_t i = 0u;
+ i += ts::FillInPacketHeader(&header_data[i], is_payload_unit_start,
+ stream_state.packet_id, adaptation_field_flag,
+ stream_state.counters.continuity++);
+ if (is_payload_unit_start) {
+ size_t adaptation_field_size = adaptation_field_min_size;
+ if (adaptation_field_flag) {
+ adaptation_field_size = ts::FillInAdaptationField(
+ &header_data[i], random_access, adaptation_field_min_size);
+ i += adaptation_field_size;
+ DCHECK_GE(adaptation_field_size, adaptation_field_min_size);
+ }
+ std::memcpy(&header_data[i], elementary_stream_packet.header().data(),
+ elementary_stream_packet.header().size());
+ i += elementary_stream_packet.header().size();
+ std::memcpy(&header_data[i],
+ elementary_stream_packet.unit_header().data(),
+ elementary_stream_packet.unit_header().size());
+ i += elementary_stream_packet.unit_header().size();
+ DCHECK_EQ(header_min_size + adaptation_field_size, i);
+ } else if (is_payload_unit_end) {
+ if (adaptation_field_flag) {
+ // Fill in an adaptation field only for padding.
+ i += ts::FillInAdaptationField(&header_data[i], false,
+ adaptation_field_min_size);
+ }
+ DCHECK_EQ(header_min_size + adaptation_field_min_size, i);
+ }
+
+ // Delegate the packet.
+ WiFiDisplayTransportStreamPacket packet(
+ header_data, i,
+ elementary_stream_packet.unit().end() - remaining_unit_size);
+ DCHECK_LE(packet.payload().size(), remaining_unit_size);
+ remaining_unit_size -= packet.payload().size();
+ if (!OnPacketizedTransportStreamPacket(
+ packet, flush && remaining_unit_size == 0u)) {
+ return false;
+ }
+
+ // Prepare for the next packet.
+ is_payload_unit_end =
+ remaining_unit_size <=
+ WiFiDisplayTransportStreamPacket::kPacketSize - ts::kPacketHeaderSize;
+ is_payload_unit_start = false;
+ } while (remaining_unit_size > 0u);
+
+ DCHECK_EQ(0u, remaining_unit_size);
+
+ return true;
+}
+
+bool WiFiDisplayTransportStreamPacketizer::EncodeMetaInformation(bool flush) {
+ DCHECK(CalledOnValidThread());
+
+ return (EncodeProgramAssociationTable(false) &&
+ EncodeProgramMapTables(false) && EncodeProgramClockReference(flush));
+}
+
+bool WiFiDisplayTransportStreamPacketizer::EncodeProgramAssociationTable(
+ bool flush) {
+ DCHECK(CalledOnValidThread());
+
+ const uint16_t transport_stream_id = 0x0001u;
+
+ uint8_t header_data[WiFiDisplayTransportStreamPacket::kPacketSize];
+ size_t i = 0u;
+
+ // Fill in a packet header.
+ i += ts::FillInPacketHeader(&header_data[i], true,
+ widi::kProgramAssociationTablePacketId, false,
+ counters_.program_association_table_continuity++);
+
+ // Fill in a minimal table pointer.
+ i += psi::FillInTablePointer(&header_data[i], 0u);
+
+ // Reserve space for a table header.
+ const size_t table_header_index = i;
+ i += psi::kTableHeaderSize;
+
+ // Fill in a table syntax.
+ const uint8_t version_number = 0u;
+ i += psi::FillInTableSyntax(&header_data[i], transport_stream_id,
+ version_number);
+
+ // Fill in program association table data.
+ i += psi::FillInProgramAssociationTableEntry(&header_data[i],
+ psi::kFirstProgramNumber,
+ widi::kProgramMapTablePacketId);
+
+ // Fill in a table header and a CRC now that the table size is known.
+ i += psi::FillInTableHeaderAndCrc(&header_data[table_header_index],
+ &header_data[i],
+ psi::kProgramAssociationTableId);
+
+ // Delegate the packet.
+ return OnPacketizedTransportStreamPacket(
+ WiFiDisplayTransportStreamPacket(header_data, i), flush);
+}
+
+bool WiFiDisplayTransportStreamPacketizer::EncodeProgramClockReference(
+ bool flush) {
+ DCHECK(CalledOnValidThread());
+
+ program_clock_reference_ = base::TimeTicks::Now();
+
+ uint8_t header_data[ts::kPacketHeaderSize + ts::kAdaptationFieldLengthSize +
+ ts::kAdaptationFieldFlagsSize +
+ ts::kProgramClockReferenceSize];
+ size_t i = 0u;
+
+ // Fill in a packet header.
+ i += ts::FillInPacketHeader(&header_data[i], true,
+ widi::kProgramClockReferencePacketId, true,
+ counters_.program_clock_reference_continuity++);
+
+ // Fill in an adaptation field.
+ i += ts::FillInAdaptationFieldLengthFromSize(
+ &header_data[i], WiFiDisplayTransportStreamPacket::kPacketSize - i);
+ i += ts::FillInAdaptationFieldFlags(&header_data[i], false,
+ program_clock_reference_);
+ i += ts::FillInProgramClockReference(&header_data[i],
+ program_clock_reference_);
+
+ DCHECK_EQ(std::end(header_data), header_data + i);
+
+ // Delegate the packet.
+ return OnPacketizedTransportStreamPacket(
+ WiFiDisplayTransportStreamPacket(header_data, i), flush);
+}
+
+bool WiFiDisplayTransportStreamPacketizer::EncodeProgramMapTables(bool flush) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!stream_states_.empty());
+
+ const uint16_t program_number = psi::kFirstProgramNumber;
+
+ uint8_t header_data[WiFiDisplayTransportStreamPacket::kPacketSize];
+ size_t i = 0u;
+
+ // Fill in a packet header.
+ i += ts::FillInPacketHeader(&header_data[i], true,
+ widi::kProgramMapTablePacketId, false,
+ counters_.program_map_table_continuity++);
+
+ // Fill in a minimal table pointer.
+ i += psi::FillInTablePointer(&header_data[i], 0u);
+
+ // Reserve space for a table header.
+ const size_t table_header_index = i;
+ i += psi::kTableHeaderSize;
+
+ // Fill in a table syntax.
+ i += psi::FillInTableSyntax(&header_data[i], program_number,
+ counters_.program_map_table_version);
+
+ // Fill in program map table data.
+ i += psi::FillInProgramMapTableData(&header_data[i],
+ widi::kProgramClockReferencePacketId);
+ for (const auto& stream_state : stream_states_) {
+ DCHECK_LE(i + psi::kProgramMapTableElementaryStreamEntryBaseSize +
+ stream_state.info_length + psi::kCrcSize,
+ WiFiDisplayTransportStreamPacket::kPacketSize);
+ i += psi::FillInProgramMapTableElementaryStreamEntry(
+ &header_data[i], stream_state.info.type(), stream_state.packet_id,
+ stream_state.info_length, stream_state.info.descriptors());
+ }
+
+ // Fill in a table header and a CRC now that the table size is known.
+ i += psi::FillInTableHeaderAndCrc(&header_data[table_header_index],
+ &header_data[i], psi::kProgramMapTableId);
+
+ // Delegate the packet.
+ return OnPacketizedTransportStreamPacket(
+ WiFiDisplayTransportStreamPacket(header_data, i), flush);
+}
+
+void WiFiDisplayTransportStreamPacketizer::NormalizeUnitTimeStamps(
+ base::TimeTicks* pts,
+ base::TimeTicks* dts) const {
+ DCHECK(CalledOnValidThread());
+
+ // Normalize a presentation time stamp.
+ if (!pts || pts->is_null())
+ return;
+ *pts += delay_for_unit_time_stamps_;
+ DCHECK_LE(program_clock_reference_, *pts);
+
+ // Normalize a decoding time stamp.
+ if (!dts || dts->is_null())
+ return;
+ *dts += delay_for_unit_time_stamps_;
+ DCHECK_LE(program_clock_reference_, *dts);
+ DCHECK_LE(*dts, *pts);
+}
+
+bool WiFiDisplayTransportStreamPacketizer::SetElementaryStreams(
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos) {
+ DCHECK(CalledOnValidThread());
+
+ std::vector<ElementaryStreamState> new_stream_states;
+ new_stream_states.reserve(stream_infos.size());
+
+ uint8_t audio_stream_id =
+ WiFiDisplayElementaryStreamPacketizer::kFirstAudioStreamId;
+ uint16_t audio_stream_packet_id = widi::kFirstAudioStreamPacketId;
+ uint8_t private_stream_1_id =
+ WiFiDisplayElementaryStreamPacketizer::kPrivateStream1Id;
+ uint16_t video_stream_packet_id = widi::kVideoStreamPacketId;
+
+ for (auto& stream_info : stream_infos) {
+ uint16_t packet_id;
+ uint8_t stream_id;
+
+ switch (stream_info.type()) {
+ case AUDIO_AAC:
+ packet_id = audio_stream_packet_id++;
+ stream_id = audio_stream_id++;
+ break;
+ case AUDIO_AC3:
+ case AUDIO_LPCM:
+ if (private_stream_1_id !=
+ WiFiDisplayElementaryStreamPacketizer::kPrivateStream1Id) {
+ return false;
+ }
+ packet_id = audio_stream_packet_id++;
+ stream_id = private_stream_1_id++;
+ break;
+ case VIDEO_H264:
+ if (video_stream_packet_id != widi::kVideoStreamPacketId)
+ return false;
+ packet_id = video_stream_packet_id++;
+ stream_id = WiFiDisplayElementaryStreamPacketizer::kFirstVideoStreamId;
+ break;
+ }
+
+ new_stream_states.emplace_back(std::move(stream_info), packet_id,
+ stream_id);
+ }
+
+ // If there are no previous states, there is no previous program map table
+ // to change, either. This ensures that the first encoded program map table
+ // has version 0.
+ if (!stream_states_.empty())
+ ++counters_.program_map_table_version;
+
+ stream_states_.swap(new_stream_states);
+
+ return true;
+}
+
+void WiFiDisplayTransportStreamPacketizer::UpdateDelayForUnitTimeStamps(
+ const base::TimeTicks& pts,
+ const base::TimeTicks& dts) {
+ DCHECK(CalledOnValidThread());
+
+ if (pts.is_null())
+ return;
+
+ const base::TimeTicks now = base::TimeTicks::Now();
+ DCHECK_LE(program_clock_reference_, now);
+
+ // Ensure that delayed time stamps are greater than or equal to now.
+ const base::TimeTicks ts_min =
+ (dts.is_null() ? pts : dts) + delay_for_unit_time_stamps_;
+ if (now > ts_min) {
+ const base::TimeDelta error = now - ts_min;
+ delay_for_unit_time_stamps_ += 2 * error;
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h
new file mode 100644
index 00000000000..0cdeb6a750d
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h
@@ -0,0 +1,176 @@
+// Copyright 2016 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.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_TRANSPORT_STREAM_PACKETIZER_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_TRANSPORT_STREAM_PACKETIZER_H_
+
+#include <vector>
+
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h"
+
+namespace extensions {
+
+class WiFiDisplayElementaryStreamInfo;
+class WiFiDisplayElementaryStreamPacket;
+
+// This class represents an MPEG Transport Stream (MPEG-TS) packet containing
+// WiFi Display elementary stream unit data or related meta information.
+class WiFiDisplayTransportStreamPacket {
+ public:
+ enum { kPacketSize = 188u };
+
+ using Part = WiFiDisplayStreamPacketPart;
+
+ // This class represents a possibly empty padding part in the end of
+ // a transport stream packet. The padding part consists of repeated bytes
+ // having the same value.
+ class PaddingPart {
+ public:
+ explicit PaddingPart(unsigned size) : size_(size) {}
+
+ unsigned size() const { return size_; }
+ uint8_t value() const { return 0xFFu; }
+
+ private:
+ const unsigned size_;
+
+ DISALLOW_COPY_AND_ASSIGN(PaddingPart);
+ };
+
+ WiFiDisplayTransportStreamPacket(const uint8_t* header_data,
+ size_t header_size);
+ WiFiDisplayTransportStreamPacket(const uint8_t* header_data,
+ size_t header_size,
+ const uint8_t* payload_data);
+
+ const Part& header() const { return header_; }
+ const Part& payload() const { return payload_; }
+ const PaddingPart& filler() const { return filler_; }
+
+ private:
+ const Part header_;
+ const Part payload_;
+ const PaddingPart filler_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplayTransportStreamPacket);
+};
+
+// The WiFi Display transport stream packetizer packetizes unit buffers to
+// MPEG Transport Stream (MPEG-TS) packets containing either meta information
+// or Packetized Elementary Stream (PES) packets containing unit data.
+//
+// Whenever a Transport Stream (TS) packet is fully created and thus ready for
+// further processing, a pure virtual member function
+// |OnPacketizedTransportStreamPacket| is called.
+class WiFiDisplayTransportStreamPacketizer : public base::NonThreadSafe {
+ public:
+ enum ElementaryStreamType : uint8_t {
+ AUDIO_AAC = 0x0Fu,
+ AUDIO_AC3 = 0x81u,
+ AUDIO_LPCM = 0x83u,
+ VIDEO_H264 = 0x1Bu,
+ };
+
+ // Fixed coding parameters for Linear Pulse-Code Modulation (LPCM) audio
+ // streams. See |WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream| for
+ // variable ones.
+ struct LPCM {
+ enum {
+ kFramesPerUnit = 6u,
+ kChannelSamplesPerFrame = 80u,
+ kChannelSamplesPerUnit = kChannelSamplesPerFrame * kFramesPerUnit
+ };
+ };
+
+ WiFiDisplayTransportStreamPacketizer(
+ const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos);
+ virtual ~WiFiDisplayTransportStreamPacketizer();
+
+ // Encodes one elementary stream unit buffer (such as one video frame or
+ // 2 * |LPCM::kChannelSamplesPerUnit| two-channel LPCM audio samples) into
+ // packets:
+ // 1) Encodes meta information into meta information packets (by calling
+ // |EncodeMetaInformation|) if needed.
+ // 2) Normalizes unit time stamps (|pts| and |dts|) so that they are never
+ // smaller than a program clock reference.
+ // 3) Encodes the elementary stream unit buffer to unit data packets.
+ // Returns false in the case of an error in which case the caller should stop
+ // encoding.
+ //
+ // In order to minimize encoding delays, |flush| should be true unless
+ // the caller is about to continue encoding immediately.
+ //
+ // Precondition: Elementary streams are configured either using a constructor
+ // or using the |SetElementaryStreams| member function.
+ bool EncodeElementaryStreamUnit(unsigned stream_index,
+ const uint8_t* unit_data,
+ size_t unit_size,
+ bool random_access,
+ base::TimeTicks pts,
+ base::TimeTicks dts,
+ bool flush);
+
+ // Encodes meta information (program association table, program map table and
+ // program clock reference). Returns false in the case of an error in which
+ // case the caller should stop encoding.
+ //
+ // The |EncodeElementaryStreamUnit| member function calls this member function
+ // when needed, thus the caller is responsible for calling this member
+ // function explicitly only if the caller does silence suppression and does
+ // thus not encode all elementary stream units by calling
+ // the |EncodeElementaryStreamUnit| member function.
+ //
+ // In order to minimize encoding delays, |flush| should be true unless
+ // the caller is about to continue encoding immediately.
+ //
+ // Precondition: Elementary streams are configured either using a constructor
+ // or using the |SetElementaryStreams| member function.
+ bool EncodeMetaInformation(bool flush);
+
+ bool SetElementaryStreams(
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos);
+
+ void DetachFromThread() { base::NonThreadSafe::DetachFromThread(); }
+
+ protected:
+ bool EncodeProgramAssociationTable(bool flush);
+ bool EncodeProgramClockReference(bool flush);
+ bool EncodeProgramMapTables(bool flush);
+
+ // Normalizes unit time stamps by delaying them in order to ensure that unit
+ // time stamps are never smaller than a program clock reference.
+ // Precondition: The |UpdateDelayForUnitTimeStamps| member function is called.
+ void NormalizeUnitTimeStamps(base::TimeTicks* pts,
+ base::TimeTicks* dts) const;
+ // Update unit time stamp delay in order to ensure that normalized unit time
+ // stamps are never smaller than a program clock reference.
+ void UpdateDelayForUnitTimeStamps(const base::TimeTicks& pts,
+ const base::TimeTicks& dts);
+
+ // Called whenever a Transport Stream (TS) packet is fully created and thus
+ // ready for further processing.
+ virtual bool OnPacketizedTransportStreamPacket(
+ const WiFiDisplayTransportStreamPacket& transport_stream_packet,
+ bool flush) = 0;
+
+ private:
+ struct ElementaryStreamState;
+
+ struct {
+ uint8_t program_association_table_continuity;
+ uint8_t program_map_table_continuity;
+ uint8_t program_map_table_version;
+ uint8_t program_clock_reference_continuity;
+ } counters_;
+ base::TimeDelta delay_for_unit_time_stamps_;
+ base::TimeTicks program_clock_reference_;
+ std::vector<ElementaryStreamState> stream_states_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_TRANSPORT_STREAM_PACKETIZER_H_
diff --git a/chromium/extensions/renderer/api/mojo_private/mojo_private_unittest.cc b/chromium/extensions/renderer/api/mojo_private/mojo_private_unittest.cc
new file mode 100644
index 00000000000..344cbe9da6c
--- /dev/null
+++ b/chromium/extensions/renderer/api/mojo_private/mojo_private_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/api_test_base.h"
+
+#include "base/macros.h"
+#include "gin/modules/module_registry.h"
+
+// A test launcher for tests for the mojoPrivate API defined in
+// extensions/test/data/mojo_private_unittest.js.
+
+namespace extensions {
+
+class MojoPrivateApiTest : public ApiTestBase {
+ public:
+ MojoPrivateApiTest() = default;
+
+ gin::ModuleRegistry* module_registry() {
+ return gin::ModuleRegistry::From(env()->context()->v8_context());
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MojoPrivateApiTest);
+};
+
+TEST_F(MojoPrivateApiTest, Define) {
+ ASSERT_NO_FATAL_FAILURE(RunTest("mojo_private_unittest.js", "testDefine"));
+ EXPECT_EQ(1u, module_registry()->available_modules().count("testModule"));
+}
+
+TEST_F(MojoPrivateApiTest, DefineRegistersModule) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("mojo_private_unittest.js", "testDefineRegistersModule"));
+ EXPECT_EQ(1u, module_registry()->available_modules().count("testModule"));
+ EXPECT_EQ(1u, module_registry()->available_modules().count("dependency"));
+}
+
+TEST_F(MojoPrivateApiTest, DefineModuleDoesNotExist) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("mojo_private_unittest.js", "testDefineModuleDoesNotExist"));
+ EXPECT_EQ(0u, module_registry()->available_modules().count("testModule"));
+ EXPECT_EQ(0u,
+ module_registry()->available_modules().count("does not exist!"));
+ EXPECT_EQ(1u, module_registry()->unsatisfied_dependencies().count(
+ "does not exist!"));
+}
+
+TEST_F(MojoPrivateApiTest, RequireAsync) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("mojo_private_unittest.js", "testRequireAsync"));
+}
+
+TEST_F(MojoPrivateApiTest, DefineAndRequire) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("mojo_private_unittest.js", "testDefineAndRequire"));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/serial/DEPS b/chromium/extensions/renderer/api/serial/DEPS
new file mode 100644
index 00000000000..e273c393ba2
--- /dev/null
+++ b/chromium/extensions/renderer/api/serial/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+device/serial",
+]
diff --git a/chromium/extensions/renderer/api/serial/data_receiver_unittest.cc b/chromium/extensions/renderer/api/serial/data_receiver_unittest.cc
new file mode 100644
index 00000000000..0c7dc000d8a
--- /dev/null
+++ b/chromium/extensions/renderer/api/serial/data_receiver_unittest.cc
@@ -0,0 +1,202 @@
+// Copyright 2014 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.
+
+#include <stdint.h>
+
+#include <queue>
+#include <utility>
+
+#include "base/macros.h"
+#include "device/serial/data_source_sender.h"
+#include "device/serial/data_stream.mojom.h"
+#include "extensions/renderer/api_test_base.h"
+#include "gin/dictionary.h"
+#include "gin/wrappable.h"
+#include "grit/extensions_renderer_resources.h"
+#include "mojo/edk/js/handle.h"
+
+namespace extensions {
+
+class DataReceiverFactory : public gin::Wrappable<DataReceiverFactory> {
+ public:
+ using Callback = base::Callback<void(
+ mojo::InterfaceRequest<device::serial::DataSource>,
+ mojo::InterfacePtr<device::serial::DataSourceClient>)>;
+ static gin::Handle<DataReceiverFactory> Create(v8::Isolate* isolate,
+ const Callback& callback) {
+ return gin::CreateHandle(isolate,
+ new DataReceiverFactory(callback, isolate));
+ }
+
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override {
+ return Wrappable<DataReceiverFactory>::GetObjectTemplateBuilder(isolate)
+ .SetMethod("create", &DataReceiverFactory::CreateReceiver);
+ }
+
+ gin::Dictionary CreateReceiver() {
+ mojo::InterfacePtr<device::serial::DataSource> sink;
+ mojo::InterfacePtr<device::serial::DataSourceClient> client;
+ mojo::InterfaceRequest<device::serial::DataSourceClient> client_request =
+ mojo::GetProxy(&client);
+ callback_.Run(mojo::GetProxy(&sink), std::move(client));
+
+ gin::Dictionary result = gin::Dictionary::CreateEmpty(isolate_);
+ result.Set("source", sink.PassInterface().PassHandle().release());
+ result.Set("client", client_request.PassMessagePipe().release());
+ return result;
+ }
+
+ static gin::WrapperInfo kWrapperInfo;
+
+ private:
+ DataReceiverFactory(const Callback& callback, v8::Isolate* isolate)
+ : callback_(callback), isolate_(isolate) {}
+
+ base::Callback<void(mojo::InterfaceRequest<device::serial::DataSource>,
+ mojo::InterfacePtr<device::serial::DataSourceClient>)>
+ callback_;
+ v8::Isolate* isolate_;
+};
+
+gin::WrapperInfo DataReceiverFactory::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+// Runs tests defined in extensions/test/data/data_receiver_unittest.js
+class DataReceiverTest : public ApiTestBase {
+ public:
+ DataReceiverTest() {}
+
+ void SetUp() override {
+ ApiTestBase::SetUp();
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(env()->isolate(),
+ "device/serial/data_receiver_test_factory",
+ DataReceiverFactory::Create(
+ env()->isolate(),
+ base::Bind(&DataReceiverTest::CreateDataSource,
+ base::Unretained(this))).ToV8());
+ }
+
+ void TearDown() override {
+ if (sender_.get()) {
+ sender_->ShutDown();
+ sender_ = NULL;
+ }
+ ApiTestBase::TearDown();
+ }
+
+ std::queue<int32_t> error_to_send_;
+ std::queue<std::string> data_to_send_;
+
+ private:
+ void CreateDataSource(
+ mojo::InterfaceRequest<device::serial::DataSource> request,
+ mojo::InterfacePtr<device::serial::DataSourceClient> client) {
+ sender_ = new device::DataSourceSender(
+ std::move(request), std::move(client),
+ base::Bind(&DataReceiverTest::ReadyToSend, base::Unretained(this)),
+ base::Bind(base::DoNothing));
+ }
+
+ void ReadyToSend(scoped_ptr<device::WritableBuffer> buffer) {
+ if (data_to_send_.empty() && error_to_send_.empty())
+ return;
+
+ std::string data;
+ int32_t error = 0;
+ if (!data_to_send_.empty()) {
+ data = data_to_send_.front();
+ data_to_send_.pop();
+ }
+ if (!error_to_send_.empty()) {
+ error = error_to_send_.front();
+ error_to_send_.pop();
+ }
+ DCHECK(buffer->GetSize() >= static_cast<uint32_t>(data.size()));
+ memcpy(buffer->GetData(), data.c_str(), data.size());
+ if (error)
+ buffer->DoneWithError(data.size(), error);
+ else
+ buffer->Done(data.size());
+ }
+
+ scoped_refptr<device::DataSourceSender> sender_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataReceiverTest);
+};
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_Receive DISABLED_Receive
+#else
+#define MAYBE_Receive Receive
+#endif
+TEST_F(DataReceiverTest, MAYBE_Receive) {
+ data_to_send_.push("a");
+ RunTest("data_receiver_unittest.js", "testReceive");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_ReceiveError DISABLED_ReceiveError
+#else
+#define MAYBE_ReceiveError ReceiveError
+#endif
+TEST_F(DataReceiverTest, MAYBE_ReceiveError) {
+ error_to_send_.push(1);
+ RunTest("data_receiver_unittest.js", "testReceiveError");
+}
+
+TEST_F(DataReceiverTest, ReceiveDataAndError) {
+ data_to_send_.push("a");
+ data_to_send_.push("b");
+ error_to_send_.push(1);
+ RunTest("data_receiver_unittest.js", "testReceiveDataAndError");
+}
+
+TEST_F(DataReceiverTest, ReceiveErrorThenData) {
+ data_to_send_.push("");
+ data_to_send_.push("a");
+ error_to_send_.push(1);
+ RunTest("data_receiver_unittest.js", "testReceiveErrorThenData");
+}
+
+TEST_F(DataReceiverTest, ReceiveBeforeAndAfterSerialization) {
+ data_to_send_.push("a");
+ data_to_send_.push("b");
+ RunTest("data_receiver_unittest.js",
+ "testReceiveBeforeAndAfterSerialization");
+}
+
+TEST_F(DataReceiverTest, ReceiveErrorSerialization) {
+ error_to_send_.push(1);
+ error_to_send_.push(3);
+ RunTest("data_receiver_unittest.js", "testReceiveErrorSerialization");
+}
+
+TEST_F(DataReceiverTest, ReceiveDataAndErrorSerialization) {
+ data_to_send_.push("a");
+ data_to_send_.push("b");
+ error_to_send_.push(1);
+ error_to_send_.push(3);
+ RunTest("data_receiver_unittest.js", "testReceiveDataAndErrorSerialization");
+}
+
+TEST_F(DataReceiverTest, SerializeDuringReceive) {
+ data_to_send_.push("a");
+ RunTest("data_receiver_unittest.js", "testSerializeDuringReceive");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_SerializeAfterClose DISABLED_SerializeAfterClose
+#else
+#define MAYBE_SerializeAfterClose SerializeAfterClose
+#endif
+TEST_F(DataReceiverTest, MAYBE_SerializeAfterClose) {
+ data_to_send_.push("a");
+ RunTest("data_receiver_unittest.js", "testSerializeAfterClose");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/serial/data_sender_unittest.cc b/chromium/extensions/renderer/api/serial/data_sender_unittest.cc
new file mode 100644
index 00000000000..a38dd10f201
--- /dev/null
+++ b/chromium/extensions/renderer/api/serial/data_sender_unittest.cc
@@ -0,0 +1,213 @@
+// Copyright 2014 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.
+
+#include <stdint.h>
+
+#include <queue>
+#include <utility>
+
+#include "base/macros.h"
+#include "device/serial/data_sink_receiver.h"
+#include "device/serial/data_stream.mojom.h"
+#include "extensions/renderer/api_test_base.h"
+#include "grit/extensions_renderer_resources.h"
+
+namespace extensions {
+
+// Runs tests defined in extensions/test/data/data_sender_unittest.js
+class DataSenderTest : public ApiTestBase {
+ public:
+ DataSenderTest() {}
+
+ void SetUp() override {
+ ApiTestBase::SetUp();
+ service_provider()->AddService(
+ base::Bind(&DataSenderTest::CreateDataSink, base::Unretained(this)));
+ }
+
+ void TearDown() override {
+ if (receiver_.get()) {
+ receiver_->ShutDown();
+ receiver_ = NULL;
+ }
+ EXPECT_FALSE(buffer_);
+ buffer_.reset();
+ ApiTestBase::TearDown();
+ }
+
+ std::queue<int32_t> error_to_report_;
+ std::queue<std::string> expected_data_;
+
+ private:
+ void CreateDataSink(
+ mojo::InterfaceRequest<device::serial::DataSink> request) {
+ receiver_ = new device::DataSinkReceiver(
+ std::move(request),
+ base::Bind(&DataSenderTest::ReadyToReceive, base::Unretained(this)),
+ base::Bind(&DataSenderTest::OnCancel, base::Unretained(this)),
+ base::Bind(base::DoNothing));
+ }
+
+ void ReadyToReceive(scoped_ptr<device::ReadOnlyBuffer> buffer) {
+ std::string data(buffer->GetData(), buffer->GetSize());
+ if (expected_data_.empty()) {
+ buffer_ = std::move(buffer);
+ return;
+ }
+
+ std::string& expected = expected_data_.front();
+ if (expected.size() > buffer->GetSize()) {
+ EXPECT_EQ(expected.substr(0, buffer->GetSize()), data);
+ expected = expected.substr(buffer->GetSize());
+ buffer->Done(buffer->GetSize());
+ return;
+ }
+ if (expected.size() < buffer->GetSize())
+ data = data.substr(0, expected.size());
+ EXPECT_EQ(expected, data);
+ expected_data_.pop();
+ int32_t error = 0;
+ if (!error_to_report_.empty()) {
+ error = error_to_report_.front();
+ error_to_report_.pop();
+ }
+ if (error)
+ buffer->DoneWithError(data.size(), error);
+ else
+ buffer->Done(data.size());
+ }
+
+ void OnCancel(int32_t error) {
+ ASSERT_TRUE(buffer_);
+ buffer_->DoneWithError(0, error);
+ buffer_.reset();
+ }
+
+ scoped_refptr<device::DataSinkReceiver> receiver_;
+ scoped_ptr<device::ReadOnlyBuffer> buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataSenderTest);
+};
+
+TEST_F(DataSenderTest, Send) {
+ expected_data_.push("aa");
+ RunTest("data_sender_unittest.js", "testSend");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_LargeSend DISABLED_LargeSend
+#else
+#define MAYBE_LargeSend LargeSend
+#endif
+TEST_F(DataSenderTest, MAYBE_LargeSend) {
+ std::string pattern = "123";
+ std::string expected_data;
+ for (int i = 0; i < 11; i++)
+ expected_data += pattern;
+ expected_data_.push(expected_data);
+ RunTest("data_sender_unittest.js", "testLargeSend");
+}
+
+TEST_F(DataSenderTest, SendError) {
+ expected_data_.push("");
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendError");
+}
+
+TEST_F(DataSenderTest, SendErrorPartialSuccess) {
+ expected_data_.push(std::string(5, 'b'));
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorPartialSuccess");
+}
+
+TEST_F(DataSenderTest, SendErrorBetweenPackets) {
+ expected_data_.push(std::string(2, 'b'));
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorBetweenPackets");
+}
+
+TEST_F(DataSenderTest, SendErrorInSecondPacket) {
+ expected_data_.push(std::string(3, 'b'));
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorInSecondPacket");
+}
+
+TEST_F(DataSenderTest, SendErrorInLargeSend) {
+ expected_data_.push("123456789012");
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorInLargeSend");
+}
+
+TEST_F(DataSenderTest, SendErrorBeforeLargeSend) {
+ expected_data_.push(std::string(2, 'b'));
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorBeforeLargeSend");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_CancelWithoutSend DISABLED_CancelWithoutSend
+#else
+#define MAYBE_CancelWithoutSend CancelWithoutSend
+#endif
+TEST_F(DataSenderTest, MAYBE_CancelWithoutSend) {
+ RunTest("data_sender_unittest.js", "testCancelWithoutSend");
+}
+
+TEST_F(DataSenderTest, Cancel) {
+ RunTest("data_sender_unittest.js", "testCancel");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_Close DISABLED_Close
+#else
+#define MAYBE_Close Close
+#endif
+TEST_F(DataSenderTest, MAYBE_Close) {
+ RunTest("data_sender_unittest.js", "testClose");
+}
+
+TEST_F(DataSenderTest, SendAfterSerialization) {
+ expected_data_.push("aa");
+ RunTest("data_sender_unittest.js", "testSendAfterSerialization");
+}
+
+TEST_F(DataSenderTest, SendErrorAfterSerialization) {
+ expected_data_.push("");
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorAfterSerialization");
+}
+
+TEST_F(DataSenderTest, CancelAfterSerialization) {
+ RunTest("data_sender_unittest.js", "testCancelAfterSerialization");
+}
+
+TEST_F(DataSenderTest, SerializeCancelsSendsInProgress) {
+ RunTest("data_sender_unittest.js", "testSerializeCancelsSendsInProgress");
+}
+
+TEST_F(DataSenderTest, SerializeWaitsForCancel) {
+ RunTest("data_sender_unittest.js", "testSerializeWaitsForCancel");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_SerializeAfterClose DISABLED_SerializeAfterClose
+#else
+#define MAYBE_SerializeAfterClose SerializeAfterClose
+#endif
+TEST_F(DataSenderTest, MAYBE_SerializeAfterClose) {
+ RunTest("data_sender_unittest.js", "testSerializeAfterClose");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/serial/serial_api_unittest.cc b/chromium/extensions/renderer/api/serial/serial_api_unittest.cc
new file mode 100644
index 00000000000..82f8f2a36f1
--- /dev/null
+++ b/chromium/extensions/renderer/api/serial/serial_api_unittest.cc
@@ -0,0 +1,738 @@
+// Copyright 2014 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.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/thread_task_runner_handle.h"
+#include "device/serial/serial_device_enumerator.h"
+#include "device/serial/serial_service_impl.h"
+#include "device/serial/test_serial_io_handler.h"
+#include "extensions/browser/mojo/stash_backend.h"
+#include "extensions/common/mojo/keep_alive.mojom.h"
+#include "extensions/renderer/api_test_base.h"
+#include "grit/extensions_renderer_resources.h"
+
+// A test launcher for tests for the serial API defined in
+// extensions/test/data/serial_unittest.js. Each C++ test function sets up a
+// fake DeviceEnumerator or SerialIoHandler expecting or returning particular
+// values for that test.
+
+namespace extensions {
+
+namespace {
+
+class FakeSerialDeviceEnumerator : public device::SerialDeviceEnumerator {
+ mojo::Array<device::serial::DeviceInfoPtr> GetDevices() override {
+ mojo::Array<device::serial::DeviceInfoPtr> result(3);
+ result[0] = device::serial::DeviceInfo::New();
+ result[0]->path = "device";
+ result[0]->vendor_id = 1234;
+ result[0]->has_vendor_id = true;
+ result[0]->product_id = 5678;
+ result[0]->has_product_id = true;
+ result[0]->display_name = "foo";
+ result[1] = device::serial::DeviceInfo::New();
+ result[1]->path = "another_device";
+ // These IDs should be ignored.
+ result[1]->vendor_id = 1234;
+ result[1]->product_id = 5678;
+ result[2] = device::serial::DeviceInfo::New();
+ result[2]->path = "";
+ result[2]->display_name = "";
+ return result;
+ }
+};
+
+enum OptionalValue {
+ OPTIONAL_VALUE_UNSET,
+ OPTIONAL_VALUE_FALSE,
+ OPTIONAL_VALUE_TRUE,
+};
+
+device::serial::HostControlSignals GenerateControlSignals(OptionalValue dtr,
+ OptionalValue rts) {
+ device::serial::HostControlSignals result;
+ switch (dtr) {
+ case OPTIONAL_VALUE_UNSET:
+ break;
+ case OPTIONAL_VALUE_FALSE:
+ result.dtr = false;
+ result.has_dtr = true;
+ break;
+ case OPTIONAL_VALUE_TRUE:
+ result.dtr = true;
+ result.has_dtr = true;
+ break;
+ }
+ switch (rts) {
+ case OPTIONAL_VALUE_UNSET:
+ break;
+ case OPTIONAL_VALUE_FALSE:
+ result.rts = false;
+ result.has_rts = true;
+ break;
+ case OPTIONAL_VALUE_TRUE:
+ result.rts = true;
+ result.has_rts = true;
+ break;
+ }
+ return result;
+}
+
+device::serial::ConnectionOptions GenerateConnectionOptions(
+ int bitrate,
+ device::serial::DataBits data_bits,
+ device::serial::ParityBit parity_bit,
+ device::serial::StopBits stop_bits,
+ OptionalValue cts_flow_control) {
+ device::serial::ConnectionOptions result;
+ result.bitrate = bitrate;
+ result.data_bits = data_bits;
+ result.parity_bit = parity_bit;
+ result.stop_bits = stop_bits;
+ switch (cts_flow_control) {
+ case OPTIONAL_VALUE_UNSET:
+ break;
+ case OPTIONAL_VALUE_FALSE:
+ result.cts_flow_control = false;
+ result.has_cts_flow_control = true;
+ break;
+ case OPTIONAL_VALUE_TRUE:
+ result.cts_flow_control = true;
+ result.has_cts_flow_control = true;
+ break;
+ }
+ return result;
+}
+
+class TestIoHandlerBase : public device::TestSerialIoHandler {
+ public:
+ TestIoHandlerBase() : calls_(0) {}
+
+ size_t num_calls() const { return calls_; }
+
+ protected:
+ ~TestIoHandlerBase() override {}
+ void record_call() const { calls_++; }
+
+ private:
+ mutable size_t calls_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestIoHandlerBase);
+};
+
+class SetControlSignalsTestIoHandler : public TestIoHandlerBase {
+ public:
+ SetControlSignalsTestIoHandler() {}
+
+ bool SetControlSignals(
+ const device::serial::HostControlSignals& signals) override {
+ static const device::serial::HostControlSignals expected_signals[] = {
+ GenerateControlSignals(OPTIONAL_VALUE_UNSET, OPTIONAL_VALUE_UNSET),
+ GenerateControlSignals(OPTIONAL_VALUE_FALSE, OPTIONAL_VALUE_UNSET),
+ GenerateControlSignals(OPTIONAL_VALUE_TRUE, OPTIONAL_VALUE_UNSET),
+ GenerateControlSignals(OPTIONAL_VALUE_UNSET, OPTIONAL_VALUE_FALSE),
+ GenerateControlSignals(OPTIONAL_VALUE_FALSE, OPTIONAL_VALUE_FALSE),
+ GenerateControlSignals(OPTIONAL_VALUE_TRUE, OPTIONAL_VALUE_FALSE),
+ GenerateControlSignals(OPTIONAL_VALUE_UNSET, OPTIONAL_VALUE_TRUE),
+ GenerateControlSignals(OPTIONAL_VALUE_FALSE, OPTIONAL_VALUE_TRUE),
+ GenerateControlSignals(OPTIONAL_VALUE_TRUE, OPTIONAL_VALUE_TRUE),
+ };
+ if (num_calls() >= arraysize(expected_signals))
+ return false;
+
+ EXPECT_EQ(expected_signals[num_calls()].has_dtr, signals.has_dtr);
+ EXPECT_EQ(expected_signals[num_calls()].dtr, signals.dtr);
+ EXPECT_EQ(expected_signals[num_calls()].has_rts, signals.has_rts);
+ EXPECT_EQ(expected_signals[num_calls()].rts, signals.rts);
+ record_call();
+ return true;
+ }
+
+ private:
+ ~SetControlSignalsTestIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(SetControlSignalsTestIoHandler);
+};
+
+class GetControlSignalsTestIoHandler : public TestIoHandlerBase {
+ public:
+ GetControlSignalsTestIoHandler() {}
+
+ device::serial::DeviceControlSignalsPtr GetControlSignals() const override {
+ device::serial::DeviceControlSignalsPtr signals(
+ device::serial::DeviceControlSignals::New());
+ signals->dcd = num_calls() & 1;
+ signals->cts = num_calls() & 2;
+ signals->ri = num_calls() & 4;
+ signals->dsr = num_calls() & 8;
+ record_call();
+ return signals;
+ }
+
+ private:
+ ~GetControlSignalsTestIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(GetControlSignalsTestIoHandler);
+};
+
+class ConfigurePortTestIoHandler : public TestIoHandlerBase {
+ public:
+ ConfigurePortTestIoHandler() {}
+ bool ConfigurePortImpl() override {
+ static const device::serial::ConnectionOptions expected_options[] = {
+ // Each JavaScript call to chrome.serial.update only modifies a single
+ // property of the connection however this function can only check the
+ // final value of all options. The modified option is marked with "set".
+ GenerateConnectionOptions(9600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::NO,
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(
+ 57600, // set
+ device::serial::DataBits::EIGHT, device::serial::ParityBit::NO,
+ device::serial::StopBits::ONE, OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600,
+ device::serial::DataBits::SEVEN, // set
+ device::serial::ParityBit::NO,
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600,
+ device::serial::DataBits::EIGHT, // set
+ device::serial::ParityBit::NO,
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::NO, // set
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::ODD, // set
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::EVEN, // set
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::EVEN,
+ device::serial::StopBits::ONE, // set
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::EVEN,
+ device::serial::StopBits::TWO, // set
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::EVEN,
+ device::serial::StopBits::TWO,
+ OPTIONAL_VALUE_FALSE), // set
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::EVEN,
+ device::serial::StopBits::TWO,
+ OPTIONAL_VALUE_TRUE), // set
+ };
+
+ if (!TestIoHandlerBase::ConfigurePortImpl()) {
+ return false;
+ }
+
+ if (num_calls() >= arraysize(expected_options)) {
+ return false;
+ }
+
+ EXPECT_EQ(expected_options[num_calls()].bitrate, options().bitrate);
+ EXPECT_EQ(expected_options[num_calls()].data_bits, options().data_bits);
+ EXPECT_EQ(expected_options[num_calls()].parity_bit, options().parity_bit);
+ EXPECT_EQ(expected_options[num_calls()].stop_bits, options().stop_bits);
+ EXPECT_EQ(expected_options[num_calls()].has_cts_flow_control,
+ options().has_cts_flow_control);
+ EXPECT_EQ(expected_options[num_calls()].cts_flow_control,
+ options().cts_flow_control);
+ record_call();
+ return true;
+ }
+
+ private:
+ ~ConfigurePortTestIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(ConfigurePortTestIoHandler);
+};
+
+class FlushTestIoHandler : public TestIoHandlerBase {
+ public:
+ FlushTestIoHandler() {}
+
+ bool Flush() const override {
+ record_call();
+ return true;
+ }
+
+ private:
+ ~FlushTestIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(FlushTestIoHandler);
+};
+
+class FailToConnectTestIoHandler : public TestIoHandlerBase {
+ public:
+ FailToConnectTestIoHandler() {}
+ void Open(const std::string& port,
+ const device::serial::ConnectionOptions& options,
+ const OpenCompleteCallback& callback) override {
+ callback.Run(false);
+ return;
+ }
+
+ private:
+ ~FailToConnectTestIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(FailToConnectTestIoHandler);
+};
+
+class FailToGetInfoTestIoHandler : public TestIoHandlerBase {
+ public:
+ explicit FailToGetInfoTestIoHandler(int times_to_succeed)
+ : times_to_succeed_(times_to_succeed) {}
+ device::serial::ConnectionInfoPtr GetPortInfo() const override {
+ if (times_to_succeed_-- > 0)
+ return device::TestSerialIoHandler::GetPortInfo();
+ return device::serial::ConnectionInfoPtr();
+ }
+
+ private:
+ ~FailToGetInfoTestIoHandler() override {}
+
+ mutable int times_to_succeed_;
+
+ DISALLOW_COPY_AND_ASSIGN(FailToGetInfoTestIoHandler);
+};
+
+class SendErrorTestIoHandler : public TestIoHandlerBase {
+ public:
+ explicit SendErrorTestIoHandler(device::serial::SendError error)
+ : error_(error) {}
+
+ void WriteImpl() override { QueueWriteCompleted(0, error_); }
+
+ private:
+ ~SendErrorTestIoHandler() override {}
+
+ device::serial::SendError error_;
+
+ DISALLOW_COPY_AND_ASSIGN(SendErrorTestIoHandler);
+};
+
+class FixedDataReceiveTestIoHandler : public TestIoHandlerBase {
+ public:
+ explicit FixedDataReceiveTestIoHandler(const std::string& data)
+ : data_(data) {}
+
+ void ReadImpl() override {
+ if (pending_read_buffer_len() < data_.size())
+ return;
+ memcpy(pending_read_buffer(), data_.c_str(), data_.size());
+ QueueReadCompleted(static_cast<uint32_t>(data_.size()),
+ device::serial::ReceiveError::NONE);
+ }
+
+ private:
+ ~FixedDataReceiveTestIoHandler() override {}
+
+ const std::string data_;
+
+ DISALLOW_COPY_AND_ASSIGN(FixedDataReceiveTestIoHandler);
+};
+
+class ReceiveErrorTestIoHandler : public TestIoHandlerBase {
+ public:
+ explicit ReceiveErrorTestIoHandler(device::serial::ReceiveError error)
+ : error_(error) {}
+
+ void ReadImpl() override { QueueReadCompleted(0, error_); }
+
+ private:
+ ~ReceiveErrorTestIoHandler() override {}
+
+ device::serial::ReceiveError error_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReceiveErrorTestIoHandler);
+};
+
+class SendDataWithErrorIoHandler : public TestIoHandlerBase {
+ public:
+ SendDataWithErrorIoHandler() : sent_error_(false) {}
+ void WriteImpl() override {
+ if (sent_error_) {
+ WriteCompleted(pending_write_buffer_len(),
+ device::serial::SendError::NONE);
+ return;
+ }
+ sent_error_ = true;
+ // We expect the JS test code to send a 4 byte buffer.
+ ASSERT_LT(2u, pending_write_buffer_len());
+ WriteCompleted(2, device::serial::SendError::SYSTEM_ERROR);
+ }
+
+ private:
+ ~SendDataWithErrorIoHandler() override {}
+
+ bool sent_error_;
+
+ DISALLOW_COPY_AND_ASSIGN(SendDataWithErrorIoHandler);
+};
+
+class BlockSendsForeverSendIoHandler : public TestIoHandlerBase {
+ public:
+ BlockSendsForeverSendIoHandler() {}
+ void WriteImpl() override {}
+
+ private:
+ ~BlockSendsForeverSendIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(BlockSendsForeverSendIoHandler);
+};
+
+} // namespace
+
+class SerialApiTest : public ApiTestBase {
+ public:
+ SerialApiTest() {}
+
+ void SetUp() override {
+ ApiTestBase::SetUp();
+ stash_backend_.reset(new StashBackend(base::Closure()));
+ PrepareEnvironment(api_test_env(), stash_backend_.get());
+ }
+
+ void PrepareEnvironment(ApiTestEnvironment* environment,
+ StashBackend* stash_backend) {
+ environment->env()->RegisterModule("serial", IDR_SERIAL_CUSTOM_BINDINGS_JS);
+ environment->service_provider()->AddService<device::serial::SerialService>(
+ base::Bind(&SerialApiTest::CreateSerialService,
+ base::Unretained(this)));
+ environment->service_provider()->AddService(base::Bind(
+ &StashBackend::BindToRequest, base::Unretained(stash_backend)));
+ environment->service_provider()->IgnoreServiceRequests<KeepAlive>();
+ }
+
+ scoped_refptr<TestIoHandlerBase> io_handler_;
+
+ scoped_ptr<StashBackend> stash_backend_;
+
+ private:
+ scoped_refptr<device::SerialIoHandler> GetIoHandler() {
+ if (!io_handler_.get())
+ io_handler_ = new TestIoHandlerBase;
+ return io_handler_;
+ }
+
+ void CreateSerialService(
+ mojo::InterfaceRequest<device::serial::SerialService> request) {
+ new device::SerialServiceImpl(
+ new device::SerialConnectionFactory(
+ base::Bind(&SerialApiTest::GetIoHandler, base::Unretained(this)),
+ base::ThreadTaskRunnerHandle::Get()),
+ scoped_ptr<device::SerialDeviceEnumerator>(
+ new FakeSerialDeviceEnumerator),
+ std::move(request));
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(SerialApiTest);
+};
+
+TEST_F(SerialApiTest, GetDevices) {
+ RunTest("serial_unittest.js", "testGetDevices");
+}
+
+TEST_F(SerialApiTest, ConnectFail) {
+ io_handler_ = new FailToConnectTestIoHandler;
+ RunTest("serial_unittest.js", "testConnectFail");
+}
+
+TEST_F(SerialApiTest, GetInfoFailOnConnect) {
+ io_handler_ = new FailToGetInfoTestIoHandler(0);
+ RunTest("serial_unittest.js", "testGetInfoFailOnConnect");
+}
+
+TEST_F(SerialApiTest, Connect) {
+ RunTest("serial_unittest.js", "testConnect");
+}
+
+TEST_F(SerialApiTest, ConnectDefaultOptions) {
+ RunTest("serial_unittest.js", "testConnectDefaultOptions");
+}
+
+TEST_F(SerialApiTest, ConnectInvalidBitrate) {
+ RunTest("serial_unittest.js", "testConnectInvalidBitrate");
+}
+
+TEST_F(SerialApiTest, GetInfo) {
+ RunTest("serial_unittest.js", "testGetInfo");
+}
+
+TEST_F(SerialApiTest, GetInfoAfterSerialization) {
+ RunTest("serial_unittest.js", "testGetInfoAfterSerialization");
+}
+
+TEST_F(SerialApiTest, GetInfoFailToGetPortInfo) {
+ io_handler_ = new FailToGetInfoTestIoHandler(1);
+ RunTest("serial_unittest.js", "testGetInfoFailToGetPortInfo");
+}
+
+TEST_F(SerialApiTest, GetConnections) {
+ RunTest("serial_unittest.js", "testGetConnections");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_GetControlSignals DISABLED_GetControlSignals
+#else
+#define MAYBE_GetControlSignals GetControlSignals
+#endif
+TEST_F(SerialApiTest, MAYBE_GetControlSignals) {
+ io_handler_ = new GetControlSignalsTestIoHandler;
+ RunTest("serial_unittest.js", "testGetControlSignals");
+ EXPECT_EQ(16u, io_handler_->num_calls());
+}
+
+TEST_F(SerialApiTest, SetControlSignals) {
+ io_handler_ = new SetControlSignalsTestIoHandler;
+ RunTest("serial_unittest.js", "testSetControlSignals");
+ EXPECT_EQ(9u, io_handler_->num_calls());
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_Update DISABLED_Update
+#else
+#define MAYBE_Update Update
+#endif
+TEST_F(SerialApiTest, MAYBE_Update) {
+ io_handler_ = new ConfigurePortTestIoHandler;
+ RunTest("serial_unittest.js", "testUpdate");
+ EXPECT_EQ(11u, io_handler_->num_calls());
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_UpdateAcrossSerialization DISABLED_UpdateAcrossSerialization
+#else
+#define MAYBE_UpdateAcrossSerialization UpdateAcrossSerialization
+#endif
+TEST_F(SerialApiTest, MAYBE_UpdateAcrossSerialization) {
+ io_handler_ = new ConfigurePortTestIoHandler;
+ RunTest("serial_unittest.js", "testUpdateAcrossSerialization");
+ EXPECT_EQ(11u, io_handler_->num_calls());
+}
+
+TEST_F(SerialApiTest, UpdateInvalidBitrate) {
+ io_handler_ = new ConfigurePortTestIoHandler;
+ RunTest("serial_unittest.js", "testUpdateInvalidBitrate");
+ EXPECT_EQ(1u, io_handler_->num_calls());
+}
+
+TEST_F(SerialApiTest, Flush) {
+ io_handler_ = new FlushTestIoHandler;
+ RunTest("serial_unittest.js", "testFlush");
+ EXPECT_EQ(1u, io_handler_->num_calls());
+}
+
+TEST_F(SerialApiTest, SetPaused) {
+ RunTest("serial_unittest.js", "testSetPaused");
+}
+
+TEST_F(SerialApiTest, Echo) {
+ RunTest("serial_unittest.js", "testEcho");
+}
+
+TEST_F(SerialApiTest, EchoAfterSerialization) {
+ RunTest("serial_unittest.js", "testEchoAfterSerialization");
+}
+
+TEST_F(SerialApiTest, SendDuringExistingSend) {
+ RunTest("serial_unittest.js", "testSendDuringExistingSend");
+}
+
+TEST_F(SerialApiTest, SendAfterSuccessfulSend) {
+ RunTest("serial_unittest.js", "testSendAfterSuccessfulSend");
+}
+
+TEST_F(SerialApiTest, SendPartialSuccessWithError) {
+ io_handler_ = new SendDataWithErrorIoHandler();
+ RunTest("serial_unittest.js", "testSendPartialSuccessWithError");
+}
+
+TEST_F(SerialApiTest, SendTimeout) {
+ io_handler_ = new BlockSendsForeverSendIoHandler();
+ RunTest("serial_unittest.js", "testSendTimeout");
+}
+
+TEST_F(SerialApiTest, SendTimeoutAfterSerialization) {
+ io_handler_ = new BlockSendsForeverSendIoHandler();
+ RunTest("serial_unittest.js", "testSendTimeoutAfterSerialization");
+}
+
+TEST_F(SerialApiTest, DisableSendTimeout) {
+ io_handler_ = new BlockSendsForeverSendIoHandler();
+ RunTest("serial_unittest.js", "testDisableSendTimeout");
+}
+
+TEST_F(SerialApiTest, PausedReceive) {
+ io_handler_ = new FixedDataReceiveTestIoHandler("data");
+ RunTest("serial_unittest.js", "testPausedReceive");
+}
+
+TEST_F(SerialApiTest, PausedReceiveError) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::DEVICE_LOST);
+ RunTest("serial_unittest.js", "testPausedReceiveError");
+}
+
+TEST_F(SerialApiTest, ReceiveTimeout) {
+ RunTest("serial_unittest.js", "testReceiveTimeout");
+}
+
+TEST_F(SerialApiTest, ReceiveTimeoutAfterSerialization) {
+ RunTest("serial_unittest.js", "testReceiveTimeoutAfterSerialization");
+}
+
+TEST_F(SerialApiTest, DisableReceiveTimeout) {
+ RunTest("serial_unittest.js", "testDisableReceiveTimeout");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorDisconnected) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::DISCONNECTED);
+ RunTest("serial_unittest.js", "testReceiveErrorDisconnected");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorDeviceLost) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::DEVICE_LOST);
+ RunTest("serial_unittest.js", "testReceiveErrorDeviceLost");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorBreak) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::BREAK);
+ RunTest("serial_unittest.js", "testReceiveErrorBreak");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorFrameError) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::FRAME_ERROR);
+ RunTest("serial_unittest.js", "testReceiveErrorFrameError");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorOverrun) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::OVERRUN);
+ RunTest("serial_unittest.js", "testReceiveErrorOverrun");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorBufferOverflow) {
+ io_handler_ = new ReceiveErrorTestIoHandler(
+ device::serial::ReceiveError::BUFFER_OVERFLOW);
+ RunTest("serial_unittest.js", "testReceiveErrorBufferOverflow");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorParityError) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::PARITY_ERROR);
+ RunTest("serial_unittest.js", "testReceiveErrorParityError");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorSystemError) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::SYSTEM_ERROR);
+ RunTest("serial_unittest.js", "testReceiveErrorSystemError");
+}
+
+TEST_F(SerialApiTest, SendErrorDisconnected) {
+ io_handler_ =
+ new SendErrorTestIoHandler(device::serial::SendError::DISCONNECTED);
+ RunTest("serial_unittest.js", "testSendErrorDisconnected");
+}
+
+TEST_F(SerialApiTest, SendErrorSystemError) {
+ io_handler_ =
+ new SendErrorTestIoHandler(device::serial::SendError::SYSTEM_ERROR);
+ RunTest("serial_unittest.js", "testSendErrorSystemError");
+}
+
+TEST_F(SerialApiTest, DisconnectUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testDisconnectUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, GetInfoUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testGetInfoUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, UpdateUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testUpdateUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, SetControlSignalsUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testSetControlSignalsUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, GetControlSignalsUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testGetControlSignalsUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, FlushUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testFlushUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, SetPausedUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testSetPausedUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, SendUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testSendUnknownConnectionId");
+}
+
+// Note: these tests are disabled, since there is no good story for persisting
+// the stashed handles when an extension process is shut down. See
+// https://crbug.com/538774
+TEST_F(SerialApiTest, DISABLED_StashAndRestoreDuringEcho) {
+ ASSERT_NO_FATAL_FAILURE(RunTest("serial_unittest.js", "testSendAndStash"));
+ scoped_ptr<ModuleSystemTestEnvironment> new_env(CreateEnvironment());
+ ApiTestEnvironment new_api_test_env(new_env.get());
+ PrepareEnvironment(&new_api_test_env, stash_backend_.get());
+ new_api_test_env.RunTest("serial_unittest.js", "testRestoreAndReceive");
+}
+
+TEST_F(SerialApiTest, DISABLED_StashAndRestoreDuringEchoError) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::DEVICE_LOST);
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("serial_unittest.js", "testRestoreAndReceiveErrorSetUp"));
+ scoped_ptr<ModuleSystemTestEnvironment> new_env(CreateEnvironment());
+ ApiTestEnvironment new_api_test_env(new_env.get());
+ PrepareEnvironment(&new_api_test_env, stash_backend_.get());
+ new_api_test_env.RunTest("serial_unittest.js", "testRestoreAndReceiveError");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_StashAndRestoreNoConnections DISABLED_StashAndRestoreNoConnections
+#else
+#define MAYBE_StashAndRestoreNoConnections StashAndRestoreNoConnections
+#endif
+TEST_F(SerialApiTest, MAYBE_StashAndRestoreNoConnections) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("serial_unittest.js", "testStashNoConnections"));
+ io_handler_ = nullptr;
+ scoped_ptr<ModuleSystemTestEnvironment> new_env(CreateEnvironment());
+ ApiTestEnvironment new_api_test_env(new_env.get());
+ PrepareEnvironment(&new_api_test_env, stash_backend_.get());
+ new_api_test_env.RunTest("serial_unittest.js", "testRestoreNoConnections");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api_activity_logger.cc b/chromium/extensions/renderer/api_activity_logger.cc
new file mode 100644
index 00000000000..e78c27452f1
--- /dev/null
+++ b/chromium/extensions/renderer/api_activity_logger.cc
@@ -0,0 +1,82 @@
+// Copyright 2014 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.
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/activity_log_converter_strategy.h"
+#include "extensions/renderer/api_activity_logger.h"
+#include "extensions/renderer/script_context.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+APIActivityLogger::APIActivityLogger(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("LogEvent", base::Bind(&APIActivityLogger::LogEvent));
+ RouteFunction("LogAPICall", base::Bind(&APIActivityLogger::LogAPICall));
+}
+
+// static
+void APIActivityLogger::LogAPICall(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ LogInternal(APICALL, args);
+}
+
+// static
+void APIActivityLogger::LogEvent(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ LogInternal(EVENT, args);
+}
+
+// static
+void APIActivityLogger::LogInternal(
+ const CallType call_type,
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_GT(args.Length(), 2);
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsString());
+ CHECK(args[2]->IsArray());
+
+ std::string ext_id = *v8::String::Utf8Value(args[0]);
+ ExtensionHostMsg_APIActionOrEvent_Params params;
+ params.api_call = *v8::String::Utf8Value(args[1]);
+ if (args.Length() == 4) // Extras are optional.
+ params.extra = *v8::String::Utf8Value(args[3]);
+ else
+ params.extra = "";
+
+ // Get the array of api call arguments.
+ v8::Local<v8::Array> arg_array = v8::Local<v8::Array>::Cast(args[2]);
+ if (arg_array->Length() > 0) {
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ ActivityLogConverterStrategy strategy;
+ converter->SetFunctionAllowed(true);
+ converter->SetStrategy(&strategy);
+ scoped_ptr<base::ListValue> arg_list(new base::ListValue());
+ for (size_t i = 0; i < arg_array->Length(); ++i) {
+ arg_list->Set(
+ i,
+ converter->FromV8Value(arg_array->Get(i),
+ args.GetIsolate()->GetCurrentContext()));
+ }
+ params.arguments.Swap(arg_list.get());
+ }
+
+ if (call_type == APICALL) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_AddAPIActionToActivityLog(ext_id, params));
+ } else if (call_type == EVENT) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_AddEventToActivityLog(ext_id, params));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api_activity_logger.h b/chromium/extensions/renderer/api_activity_logger.h
new file mode 100644
index 00000000000..63a4699a88d
--- /dev/null
+++ b/chromium/extensions/renderer/api_activity_logger.h
@@ -0,0 +1,52 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_API_ACTIVITY_LOGGER_H_
+#define EXTENSIONS_RENDERER_API_ACTIVITY_LOGGER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// Used to log extension API calls and events that are implemented with custom
+// bindings.The actions are sent via IPC to extensions::ActivityLog for
+// recording and display.
+class APIActivityLogger : public ObjectBackedNativeHandler {
+ public:
+ explicit APIActivityLogger(ScriptContext* context);
+
+ private:
+ // Used to distinguish API calls & events from each other in LogInternal.
+ enum CallType { APICALL, EVENT };
+
+ // This is ultimately invoked in bindings.js with JavaScript arguments.
+ // arg0 - extension ID as a string
+ // arg1 - API call name as a string
+ // arg2 - arguments to the API call
+ // arg3 - any extra logging info as a string (optional)
+ static void LogAPICall(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // This is ultimately invoked in bindings.js with JavaScript arguments.
+ // arg0 - extension ID as a string
+ // arg1 - Event name as a string
+ // arg2 - Event arguments
+ // arg3 - any extra logging info as a string (optional)
+ static void LogEvent(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // LogAPICall and LogEvent are really the same underneath except for
+ // how they are ultimately dispatched to the log.
+ static void LogInternal(const CallType call_type,
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(APIActivityLogger);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_ACTIVITY_LOGGER_H_
diff --git a/chromium/extensions/renderer/api_definitions_natives.cc b/chromium/extensions/renderer/api_definitions_natives.cc
new file mode 100644
index 00000000000..96f1f2c6eb8
--- /dev/null
+++ b/chromium/extensions/renderer/api_definitions_natives.cc
@@ -0,0 +1,37 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/api_definitions_natives.h"
+
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+ApiDefinitionsNatives::ApiDefinitionsNatives(Dispatcher* dispatcher,
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context), dispatcher_(dispatcher) {
+ RouteFunction(
+ "GetExtensionAPIDefinitionsForTest",
+ base::Bind(&ApiDefinitionsNatives::GetExtensionAPIDefinitionsForTest,
+ base::Unretained(this)));
+}
+
+void ApiDefinitionsNatives::GetExtensionAPIDefinitionsForTest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ std::vector<std::string> apis;
+ const FeatureProvider* feature_provider = FeatureProvider::GetAPIFeatures();
+ for (const auto& map_entry : feature_provider->GetAllFeatures()) {
+ if (!feature_provider->GetParent(map_entry.second.get()) &&
+ context()->GetAvailability(map_entry.first).is_available()) {
+ apis.push_back(map_entry.first);
+ }
+ }
+ args.GetReturnValue().Set(
+ dispatcher_->v8_schema_registry()->GetSchemas(apis));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api_definitions_natives.h b/chromium/extensions/renderer/api_definitions_natives.h
new file mode 100644
index 00000000000..dcc1b732feb
--- /dev/null
+++ b/chromium/extensions/renderer/api_definitions_natives.h
@@ -0,0 +1,34 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_API_DEFINITIONS_NATIVES_H_
+#define EXTENSIONS_RENDERER_API_DEFINITIONS_NATIVES_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class Dispatcher;
+class ScriptContext;
+
+// Native functions for JS to get access to the schemas for extension APIs.
+class ApiDefinitionsNatives : public ObjectBackedNativeHandler {
+ public:
+ ApiDefinitionsNatives(Dispatcher* dispatcher, ScriptContext* context);
+
+ private:
+ // Returns the list of all schemas that are available to the calling context.
+ void GetExtensionAPIDefinitionsForTest(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Not owned.
+ Dispatcher* dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApiDefinitionsNatives);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DEFINITIONS_NATIVES_H_
diff --git a/chromium/extensions/renderer/api_test_base.cc b/chromium/extensions/renderer/api_test_base.cc
new file mode 100644
index 00000000000..81ec07f18d6
--- /dev/null
+++ b/chromium/extensions/renderer/api_test_base.cc
@@ -0,0 +1,240 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/api_test_base.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/run_loop.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/process_info_native_handler.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+#include "mojo/edk/js/core.h"
+#include "mojo/edk/js/handle.h"
+#include "mojo/edk/js/support.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace extensions {
+namespace {
+
+// Natives for the implementation of the unit test version of chrome.test. Calls
+// the provided |quit_closure| when either notifyPass or notifyFail is called.
+class TestNatives : public gin::Wrappable<TestNatives> {
+ public:
+ static gin::Handle<TestNatives> Create(v8::Isolate* isolate,
+ const base::Closure& quit_closure) {
+ return gin::CreateHandle(isolate, new TestNatives(quit_closure));
+ }
+
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override {
+ return Wrappable<TestNatives>::GetObjectTemplateBuilder(isolate)
+ .SetMethod("Log", &TestNatives::Log)
+ .SetMethod("NotifyPass", &TestNatives::NotifyPass)
+ .SetMethod("NotifyFail", &TestNatives::NotifyFail);
+ }
+
+ void Log(const std::string& value) { logs_ += value + "\n"; }
+ void NotifyPass() { FinishTesting(); }
+
+ void NotifyFail(const std::string& message) {
+ FinishTesting();
+ FAIL() << logs_ << message;
+ }
+
+ void FinishTesting() {
+ base::MessageLoop::current()->PostTask(FROM_HERE, quit_closure_);
+ }
+
+ static gin::WrapperInfo kWrapperInfo;
+
+ private:
+ explicit TestNatives(const base::Closure& quit_closure)
+ : quit_closure_(quit_closure) {}
+
+ const base::Closure quit_closure_;
+ std::string logs_;
+};
+
+gin::WrapperInfo TestNatives::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+} // namespace
+
+gin::WrapperInfo TestServiceProvider::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+gin::Handle<TestServiceProvider> TestServiceProvider::Create(
+ v8::Isolate* isolate) {
+ return gin::CreateHandle(isolate, new TestServiceProvider());
+}
+
+TestServiceProvider::~TestServiceProvider() {
+}
+
+gin::ObjectTemplateBuilder TestServiceProvider::GetObjectTemplateBuilder(
+ v8::Isolate* isolate) {
+ return Wrappable<TestServiceProvider>::GetObjectTemplateBuilder(isolate)
+ .SetMethod("connectToService", &TestServiceProvider::ConnectToService);
+}
+
+mojo::Handle TestServiceProvider::ConnectToService(
+ const std::string& service_name) {
+ EXPECT_EQ(1u, service_factories_.count(service_name))
+ << "Unregistered service " << service_name << " requested.";
+ mojo::MessagePipe pipe;
+ std::map<std::string,
+ base::Callback<void(mojo::ScopedMessagePipeHandle)> >::iterator it =
+ service_factories_.find(service_name);
+ if (it != service_factories_.end())
+ it->second.Run(std::move(pipe.handle0));
+ return pipe.handle1.release();
+}
+
+TestServiceProvider::TestServiceProvider() {
+}
+
+// static
+void TestServiceProvider::IgnoreHandle(mojo::ScopedMessagePipeHandle handle) {
+}
+
+ApiTestEnvironment::ApiTestEnvironment(
+ ModuleSystemTestEnvironment* environment) {
+ env_ = environment;
+ InitializeEnvironment();
+ RegisterModules();
+}
+
+ApiTestEnvironment::~ApiTestEnvironment() {
+}
+
+void ApiTestEnvironment::RegisterModules() {
+ v8_schema_registry_.reset(new V8SchemaRegistry);
+ const std::vector<std::pair<std::string, int> > resources =
+ Dispatcher::GetJsResources();
+ for (std::vector<std::pair<std::string, int> >::const_iterator resource =
+ resources.begin();
+ resource != resources.end();
+ ++resource) {
+ if (resource->first != "test_environment_specific_bindings")
+ env()->RegisterModule(resource->first, resource->second);
+ }
+ Dispatcher::RegisterNativeHandlers(env()->module_system(),
+ env()->context(),
+ NULL,
+ NULL,
+ v8_schema_registry_.get());
+ env()->module_system()->RegisterNativeHandler(
+ "process",
+ scoped_ptr<NativeHandler>(new ProcessInfoNativeHandler(
+ env()->context(),
+ env()->context()->GetExtensionID(),
+ env()->context()->GetContextTypeDescription(),
+ false,
+ false,
+ 2,
+ false)));
+ env()->RegisterTestFile("test_environment_specific_bindings",
+ "unit_test_environment_specific_bindings.js");
+
+ env()->OverrideNativeHandler("activityLogger",
+ "exports.$set('LogAPICall', function() {});");
+ env()->OverrideNativeHandler(
+ "apiDefinitions",
+ "exports.$set('GetExtensionAPIDefinitionsForTest',"
+ "function() { return [] });");
+ env()->OverrideNativeHandler(
+ "event_natives",
+ "exports.$set('AttachEvent', function() {});"
+ "exports.$set('DetachEvent', function() {});"
+ "exports.$set('AttachFilteredEvent', function() {});"
+ "exports.$set('AttachFilteredEvent', function() {});"
+ "exports.$set('MatchAgainstEventFilter', function() { return [] });");
+
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(env()->isolate(), mojo::edk::js::Core::kModuleName,
+ mojo::edk::js::Core::GetModule(env()->isolate()));
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(env()->isolate(), mojo::edk::js::Support::kModuleName,
+ mojo::edk::js::Support::GetModule(env()->isolate()));
+ gin::Handle<TestServiceProvider> service_provider =
+ TestServiceProvider::Create(env()->isolate());
+ service_provider_ = service_provider.get();
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(env()->isolate(),
+ "content/public/renderer/frame_service_registry",
+ service_provider.ToV8());
+}
+
+void ApiTestEnvironment::InitializeEnvironment() {
+ gin::Dictionary global(env()->isolate(),
+ env()->context()->v8_context()->Global());
+ gin::Dictionary navigator(gin::Dictionary::CreateEmpty(env()->isolate()));
+ navigator.Set("appVersion", base::StringPiece(""));
+ global.Set("navigator", navigator);
+ gin::Dictionary chrome(gin::Dictionary::CreateEmpty(env()->isolate()));
+ global.Set("chrome", chrome);
+ gin::Dictionary extension(gin::Dictionary::CreateEmpty(env()->isolate()));
+ chrome.Set("extension", extension);
+ gin::Dictionary runtime(gin::Dictionary::CreateEmpty(env()->isolate()));
+ chrome.Set("runtime", runtime);
+}
+
+void ApiTestEnvironment::RunTest(const std::string& file_name,
+ const std::string& test_name) {
+ env()->RegisterTestFile("testBody", file_name);
+ base::RunLoop run_loop;
+ gin::ModuleRegistry::From(env()->context()->v8_context())->AddBuiltinModule(
+ env()->isolate(),
+ "testNatives",
+ TestNatives::Create(env()->isolate(), run_loop.QuitClosure()).ToV8());
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ApiTestEnvironment::RunTestInner, base::Unretained(this),
+ test_name, run_loop.QuitClosure()));
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&ApiTestEnvironment::RunPromisesAgain,
+ base::Unretained(this)));
+ run_loop.Run();
+}
+
+void ApiTestEnvironment::RunTestInner(const std::string& test_name,
+ const base::Closure& quit_closure) {
+ v8::HandleScope scope(env()->isolate());
+ ModuleSystem::NativesEnabledScope natives_enabled(env()->module_system());
+ v8::Local<v8::Value> result =
+ env()->module_system()->CallModuleMethod("testBody", test_name);
+ if (!result->IsTrue()) {
+ base::MessageLoop::current()->PostTask(FROM_HERE, quit_closure);
+ FAIL() << "Failed to run test \"" << test_name << "\"";
+ }
+}
+
+void ApiTestEnvironment::RunPromisesAgain() {
+ v8::MicrotasksScope::PerformCheckpoint(env()->isolate());
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&ApiTestEnvironment::RunPromisesAgain,
+ base::Unretained(this)));
+}
+
+ApiTestBase::ApiTestBase() {
+}
+
+ApiTestBase::~ApiTestBase() {
+}
+
+void ApiTestBase::SetUp() {
+ ModuleSystemTest::SetUp();
+ test_env_.reset(new ApiTestEnvironment(env()));
+}
+
+void ApiTestBase::RunTest(const std::string& file_name,
+ const std::string& test_name) {
+ ExpectNoAssertionsMade();
+ test_env_->RunTest(file_name, test_name);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api_test_base.h b/chromium/extensions/renderer/api_test_base.h
new file mode 100644
index 00000000000..8b62049924c
--- /dev/null
+++ b/chromium/extensions/renderer/api_test_base.h
@@ -0,0 +1,124 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_API_TEST_BASE_H_
+#define EXTENSIONS_RENDERER_API_TEST_BASE_H_
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "extensions/renderer/module_system_test.h"
+#include "extensions/renderer/v8_schema_registry.h"
+#include "gin/handle.h"
+#include "gin/modules/module_registry.h"
+#include "gin/object_template_builder.h"
+#include "gin/wrappable.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace extensions {
+
+class V8SchemaRegistry;
+
+// A ServiceProvider that provides access from JS modules to services registered
+// by AddService() calls.
+class TestServiceProvider : public gin::Wrappable<TestServiceProvider> {
+ public:
+ static gin::Handle<TestServiceProvider> Create(v8::Isolate* isolate);
+ ~TestServiceProvider() override;
+
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override;
+
+ template <typename Interface>
+ void AddService(const base::Callback<void(mojo::InterfaceRequest<Interface>)>
+ service_factory) {
+ service_factories_.insert(std::make_pair(
+ Interface::Name_,
+ base::Bind(ForwardToServiceFactory<Interface>, service_factory)));
+ }
+
+ // Ignore requests for the Interface service.
+ template <typename Interface>
+ void IgnoreServiceRequests() {
+ service_factories_.insert(std::make_pair(
+ Interface::Name_, base::Bind(&TestServiceProvider::IgnoreHandle)));
+ }
+
+ static gin::WrapperInfo kWrapperInfo;
+
+ private:
+ TestServiceProvider();
+
+ mojo::Handle ConnectToService(const std::string& service_name);
+
+ template <typename Interface>
+ static void ForwardToServiceFactory(
+ const base::Callback<void(mojo::InterfaceRequest<Interface>)>
+ service_factory,
+ mojo::ScopedMessagePipeHandle handle) {
+ service_factory.Run(mojo::MakeRequest<Interface>(std::move(handle)));
+ }
+
+ static void IgnoreHandle(mojo::ScopedMessagePipeHandle handle);
+
+ std::map<std::string, base::Callback<void(mojo::ScopedMessagePipeHandle)> >
+ service_factories_;
+};
+
+// An environment for unit testing apps/extensions API custom bindings
+// implemented on Mojo services. This augments a ModuleSystemTestEnvironment
+// with a TestServiceProvider and other modules available in a real extensions
+// environment.
+class ApiTestEnvironment {
+ public:
+ explicit ApiTestEnvironment(ModuleSystemTestEnvironment* environment);
+ ~ApiTestEnvironment();
+ void RunTest(const std::string& file_name, const std::string& test_name);
+ TestServiceProvider* service_provider() { return service_provider_; }
+ ModuleSystemTestEnvironment* env() { return env_; }
+
+ private:
+ void RegisterModules();
+ void InitializeEnvironment();
+ void RunTestInner(const std::string& test_name,
+ const base::Closure& quit_closure);
+ void RunPromisesAgain();
+
+ ModuleSystemTestEnvironment* env_;
+ TestServiceProvider* service_provider_;
+ scoped_ptr<V8SchemaRegistry> v8_schema_registry_;
+};
+
+// A base class for unit testing apps/extensions API custom bindings implemented
+// on Mojo services. To use:
+// 1. Register test Mojo service implementations on service_provider().
+// 2. Write JS tests in extensions/test/data/test_file.js.
+// 3. Write one C++ test function for each JS test containing
+// RunTest("test_file.js", "testFunctionName").
+// See extensions/renderer/api_test_base_unittest.cc and
+// extensions/test/data/api_test_base_unittest.js for sample usage.
+class ApiTestBase : public ModuleSystemTest {
+ protected:
+ ApiTestBase();
+ ~ApiTestBase() override;
+ void SetUp() override;
+ void RunTest(const std::string& file_name, const std::string& test_name);
+
+ ApiTestEnvironment* api_test_env() { return test_env_.get(); }
+ TestServiceProvider* service_provider() {
+ return test_env_->service_provider();
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ scoped_ptr<ApiTestEnvironment> test_env_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_TEST_BASE_H_
diff --git a/chromium/extensions/renderer/api_test_base_unittest.cc b/chromium/extensions/renderer/api_test_base_unittest.cc
new file mode 100644
index 00000000000..149e34eb22a
--- /dev/null
+++ b/chromium/extensions/renderer/api_test_base_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/api_test_base.h"
+
+namespace extensions {
+
+class ApiTestBaseTest : public ApiTestBase {
+ public:
+ void SetUp() override { ApiTestBase::SetUp(); }
+};
+
+TEST_F(ApiTestBaseTest, TestEnvironment) {
+ RunTest("api_test_base_unittest.js", "testEnvironment");
+}
+
+TEST_F(ApiTestBaseTest, TestPromisesRun) {
+ RunTest("api_test_base_unittest.js", "testPromisesRun");
+}
+
+TEST_F(ApiTestBaseTest, TestCommonModulesAreAvailable) {
+ RunTest("api_test_base_unittest.js", "testCommonModulesAreAvailable");
+}
+
+TEST_F(ApiTestBaseTest, TestMojoModulesAreAvailable) {
+ RunTest("api_test_base_unittest.js", "testMojoModulesAreAvailable");
+}
+
+TEST_F(ApiTestBaseTest, TestTestBindings) {
+ RunTest("api_test_base_unittest.js", "testTestBindings");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/app_window_custom_bindings.cc b/chromium/extensions/renderer/app_window_custom_bindings.cc
new file mode 100644
index 00000000000..492731d1a82
--- /dev/null
+++ b/chromium/extensions/renderer/app_window_custom_bindings.cc
@@ -0,0 +1,82 @@
+// 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.
+
+#include "extensions/renderer/app_window_custom_bindings.h"
+
+#include "base/command_line.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/switches.h"
+#include "extensions/renderer/script_context.h"
+#include "grit/extensions_renderer_resources.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+AppWindowCustomBindings::AppWindowCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("GetFrame", base::Bind(&AppWindowCustomBindings::GetFrame,
+ base::Unretained(this)));
+
+ RouteFunction("GetWindowControlsHtmlTemplate",
+ base::Bind(&AppWindowCustomBindings::GetWindowControlsHtmlTemplate,
+ base::Unretained(this)));
+}
+
+void AppWindowCustomBindings::GetFrame(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // TODO(jeremya): convert this to IDL nocompile to get validation, and turn
+ // these argument checks into CHECK().
+ if (args.Length() != 2)
+ return;
+
+ if (!args[0]->IsInt32() || !args[1]->IsBoolean())
+ return;
+
+ int frame_id = args[0]->Int32Value();
+ bool notify_browser = args[1]->BooleanValue();
+
+ if (frame_id == MSG_ROUTING_NONE)
+ return;
+
+ content::RenderFrame* app_frame =
+ content::RenderFrame::FromRoutingID(frame_id);
+ if (!app_frame)
+ return;
+
+ if (notify_browser) {
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_AppWindowReady(
+ app_frame->GetRenderView()->GetRoutingID()));
+ }
+
+ v8::Local<v8::Value> window =
+ app_frame->GetWebFrame()->mainWorldScriptContext()->Global();
+ args.GetReturnValue().Set(window);
+}
+
+void AppWindowCustomBindings::GetWindowControlsHtmlTemplate(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 0);
+
+ v8::Local<v8::Value> result = v8::String::Empty(args.GetIsolate());
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAppWindowControls)) {
+ base::StringValue value(
+ ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(IDR_WINDOW_CONTROLS_TEMPLATE_HTML)
+ .as_string());
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ result = converter->ToV8Value(&value, context()->v8_context());
+ }
+ args.GetReturnValue().Set(result);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/app_window_custom_bindings.h b/chromium/extensions/renderer/app_window_custom_bindings.h
new file mode 100644
index 00000000000..3fbab04f0fd
--- /dev/null
+++ b/chromium/extensions/renderer/app_window_custom_bindings.h
@@ -0,0 +1,32 @@
+// 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.
+
+#ifndef EXTENSIONS_RENDERER_APP_WINDOW_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_APP_WINDOW_CUSTOM_BINDINGS_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContextSet;
+
+// Implements custom bindings for the app.window API.
+class AppWindowCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ AppWindowCustomBindings(ScriptContext* context);
+
+ private:
+ void GetFrame(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Return string containing the HTML <template> for the <window-controls>
+ // custom element.
+ void GetWindowControlsHtmlTemplate(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(AppWindowCustomBindings);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_APP_WINDOW_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/binding_generating_native_handler.cc b/chromium/extensions/renderer/binding_generating_native_handler.cc
new file mode 100644
index 00000000000..8912017e7f1
--- /dev/null
+++ b/chromium/extensions/renderer/binding_generating_native_handler.cc
@@ -0,0 +1,112 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/binding_generating_native_handler.h"
+
+#include "base/macros.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+BindingGeneratingNativeHandler::BindingGeneratingNativeHandler(
+ ScriptContext* context,
+ const std::string& api_name,
+ const std::string& bind_to)
+ : context_(context), api_name_(api_name), bind_to_(bind_to) {}
+
+v8::Local<v8::Object> BindingGeneratingNativeHandler::NewInstance() {
+ // This long sequence of commands effectively runs the JavaScript code,
+ // such that result[bind_to] is the compiled schema for |api_name|:
+ //
+ // var result = {};
+ // result[bind_to] = require('binding').Binding.create(api_name).generate();
+ // return result;
+ //
+ // Unfortunately using the v8 APIs makes that quite verbose.
+ // Each stage is marked with the code it executes.
+ v8::Isolate* isolate = context_->isolate();
+ v8::EscapableHandleScope scope(isolate);
+
+ // Convert |api_name| and |bind_to| into their v8::Strings to pass
+ // through the v8 APIs.
+ v8::Local<v8::String> v8_api_name;
+ v8::Local<v8::String> v8_bind_to;
+ if (!ToV8String(isolate, api_name_, &v8_api_name) ||
+ !ToV8String(isolate, bind_to_, &v8_bind_to)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ v8::Local<v8::Context> v8_context = context_->v8_context();
+
+ // require('binding');
+ v8::Local<v8::Object> binding_module;
+ if (!context_->module_system()->Require("binding").ToLocal(&binding_module)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ // require('binding').Binding;
+ v8::Local<v8::Value> binding_value;
+ v8::Local<v8::Object> binding;
+ if (!GetProperty(v8_context, binding_module, "Binding", &binding_value) ||
+ !binding_value->ToObject(v8_context).ToLocal(&binding)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ // require('binding').Binding.create;
+ v8::Local<v8::Value> create_binding_value;
+ if (!GetProperty(v8_context, binding, "create", &create_binding_value) ||
+ !create_binding_value->IsFunction()) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+ v8::Local<v8::Function> create_binding =
+ create_binding_value.As<v8::Function>();
+
+ // require('Binding').Binding.create(api_name);
+ v8::Local<v8::Value> argv[] = {v8_api_name};
+ v8::Local<v8::Value> binding_instance_value;
+ v8::Local<v8::Object> binding_instance;
+ if (!CallFunction(v8_context, create_binding, binding, arraysize(argv), argv,
+ &binding_instance_value) ||
+ !binding_instance_value->ToObject(v8_context)
+ .ToLocal(&binding_instance)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ // require('binding').Binding.create(api_name).generate;
+ v8::Local<v8::Value> generate_value;
+ if (!GetProperty(v8_context, binding_instance, "generate", &generate_value) ||
+ !generate_value->IsFunction()) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+ v8::Local<v8::Function> generate = generate_value.As<v8::Function>();
+
+ // require('binding').Binding.create(api_name).generate();
+ v8::Local<v8::Object> object = v8::Object::New(isolate);
+ v8::Local<v8::Value> compiled_schema;
+ if (!CallFunction(v8_context, generate, binding_instance, 0, nullptr,
+ &compiled_schema)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ // var result = {};
+ // result[bind_to] = ...;
+ if (!SetProperty(v8_context, object, v8_bind_to, compiled_schema)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+ // return result;
+ return scope.Escape(object);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/binding_generating_native_handler.h b/chromium/extensions/renderer/binding_generating_native_handler.h
new file mode 100644
index 00000000000..d4e4b0aa722
--- /dev/null
+++ b/chromium/extensions/renderer/binding_generating_native_handler.h
@@ -0,0 +1,38 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_BINDING_GENERATING_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_BINDING_GENERATING_NATIVE_HANDLER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "extensions/renderer/native_handler.h"
+
+namespace extensions {
+
+class ScriptContext;
+
+// Generates API bindings based on the JSON/IDL schemas. This is done by
+// creating a |Binding| (from binding.js) for the schema and generating the
+// bindings from that.
+class BindingGeneratingNativeHandler : public NativeHandler {
+ public:
+ // Generates binding for |api_name|, and sets the |bind_to| property on the
+ // Object returned by |NewInstance| to the generated binding.
+ BindingGeneratingNativeHandler(ScriptContext* context,
+ const std::string& api_name,
+ const std::string& bind_to);
+
+ v8::Local<v8::Object> NewInstance() override;
+
+ private:
+ ScriptContext* context_;
+ std::string api_name_;
+ std::string bind_to_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_BINDING_GENERATING_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/blob_native_handler.cc b/chromium/extensions/renderer/blob_native_handler.cc
new file mode 100644
index 00000000000..4161fbc2184
--- /dev/null
+++ b/chromium/extensions/renderer/blob_native_handler.cc
@@ -0,0 +1,54 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/blob_native_handler.h"
+
+#include "base/bind.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/platform/WebURL.h"
+#include "third_party/WebKit/public/web/WebBlob.h"
+
+namespace {
+
+// Expects a single Blob argument. Returns the Blob's UUID.
+void GetBlobUuid(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ blink::WebBlob blob = blink::WebBlob::fromV8Value(args[0]);
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(args.GetIsolate(), blob.uuid().utf8().data()));
+}
+
+} // namespace
+
+namespace extensions {
+
+BlobNativeHandler::BlobNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("GetBlobUuid", base::Bind(&GetBlobUuid));
+ RouteFunction("TakeBrowserProcessBlob",
+ base::Bind(&BlobNativeHandler::TakeBrowserProcessBlob,
+ base::Unretained(this)));
+}
+
+// Take ownership of a Blob created on the browser process. Expects the Blob's
+// UUID, type, and size as arguments. Returns the Blob we just took to
+// Javascript. The Blob reference in the browser process is dropped through
+// a separate flow to avoid leaking Blobs if the script context is destroyed.
+void BlobNativeHandler::TakeBrowserProcessBlob(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(3, args.Length());
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsString());
+ CHECK(args[2]->IsInt32());
+ std::string uuid(*v8::String::Utf8Value(args[0]));
+ std::string type(*v8::String::Utf8Value(args[1]));
+ blink::WebBlob blob =
+ blink::WebBlob::createFromUUID(blink::WebString::fromUTF8(uuid),
+ blink::WebString::fromUTF8(type),
+ args[2]->Int32Value());
+ args.GetReturnValue().Set(blob.toV8Value(
+ context()->v8_context()->Global(), args.GetIsolate()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/blob_native_handler.h b/chromium/extensions/renderer/blob_native_handler.h
new file mode 100644
index 00000000000..6f986e56647
--- /dev/null
+++ b/chromium/extensions/renderer/blob_native_handler.h
@@ -0,0 +1,30 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_BLOB_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_BLOB_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// This native handler is used to extract Blobs' UUIDs and pass them over to the
+// browser process extension implementation via argument modification. This is
+// necessary to support extension functions that take Blob parameters, as Blobs
+// are not serialized and sent over to the browser process in the normal way.
+//
+// Blobs sent via this method don't have their ref-counts incremented, so the
+// app using this technique must be sure to keep a reference.
+class BlobNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit BlobNativeHandler(ScriptContext* context);
+
+ private:
+ void TakeBrowserProcessBlob(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_BLOB_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/console.cc b/chromium/extensions/renderer/console.cc
new file mode 100644
index 00000000000..64160743f77
--- /dev/null
+++ b/chromium/extensions/renderer/console.cc
@@ -0,0 +1,127 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/console.h"
+
+#include "base/compiler_specific.h"
+#include "base/debug/alias.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/v8_helpers.h"
+
+namespace extensions {
+namespace console {
+
+using namespace v8_helpers;
+
+namespace {
+
+// Writes |message| to stack to show up in minidump, then crashes.
+void CheckWithMinidump(const std::string& message) {
+ char minidump[1024];
+ base::debug::Alias(&minidump);
+ base::snprintf(
+ minidump, arraysize(minidump), "e::console: %s", message.c_str());
+ CHECK(false) << message;
+}
+
+typedef void (*LogMethod)(content::RenderFrame* render_frame,
+ const std::string& message);
+
+void BoundLogMethodCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
+ std::string message;
+ for (int i = 0; i < info.Length(); ++i) {
+ if (i > 0)
+ message += " ";
+ message += *v8::String::Utf8Value(info[i]);
+ }
+
+ v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
+ if (context.IsEmpty()) {
+ LOG(WARNING) << "Could not log \"" << message << "\": no context given";
+ return;
+ }
+
+ ScriptContext* script_context =
+ ScriptContextSet::GetContextByV8Context(context);
+ LogMethod log_method =
+ reinterpret_cast<LogMethod>(info.Data().As<v8::External>()->Value());
+ (*log_method)(script_context ? script_context->GetRenderFrame() : nullptr,
+ message);
+}
+
+void BindLogMethod(v8::Isolate* isolate,
+ v8::Local<v8::Object> target,
+ const std::string& name,
+ LogMethod log_method) {
+ v8::Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(
+ isolate,
+ &BoundLogMethodCallback,
+ v8::External::New(isolate, reinterpret_cast<void*>(log_method)));
+ v8::Local<v8::Function> function;
+ if (!tmpl->GetFunction(isolate->GetCurrentContext()).ToLocal(&function)) {
+ LOG(FATAL) << "Could not create log function \"" << name << "\"";
+ return;
+ }
+ v8::Local<v8::String> v8_name = ToV8StringUnsafe(isolate, name);
+ if (!SetProperty(isolate->GetCurrentContext(), target, v8_name, function)) {
+ LOG(WARNING) << "Could not bind log method \"" << name << "\"";
+ }
+ SetProperty(isolate->GetCurrentContext(), target, v8_name,
+ tmpl->GetFunction());
+}
+
+} // namespace
+
+void Debug(content::RenderFrame* render_frame, const std::string& message) {
+ AddMessage(render_frame, content::CONSOLE_MESSAGE_LEVEL_DEBUG, message);
+}
+
+void Log(content::RenderFrame* render_frame, const std::string& message) {
+ AddMessage(render_frame, content::CONSOLE_MESSAGE_LEVEL_LOG, message);
+}
+
+void Warn(content::RenderFrame* render_frame, const std::string& message) {
+ AddMessage(render_frame, content::CONSOLE_MESSAGE_LEVEL_WARNING, message);
+}
+
+void Error(content::RenderFrame* render_frame, const std::string& message) {
+ AddMessage(render_frame, content::CONSOLE_MESSAGE_LEVEL_ERROR, message);
+}
+
+void Fatal(content::RenderFrame* render_frame, const std::string& message) {
+ Error(render_frame, message);
+ CheckWithMinidump(message);
+}
+
+void AddMessage(content::RenderFrame* render_frame,
+ content::ConsoleMessageLevel level,
+ const std::string& message) {
+ if (!render_frame) {
+ LOG(WARNING) << "Could not log \"" << message
+ << "\": no render frame found";
+ } else {
+ render_frame->AddMessageToConsole(level, message);
+ }
+}
+
+v8::Local<v8::Object> AsV8Object(v8::Isolate* isolate) {
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Local<v8::Object> console_object = v8::Object::New(isolate);
+ BindLogMethod(isolate, console_object, "debug", &Debug);
+ BindLogMethod(isolate, console_object, "log", &Log);
+ BindLogMethod(isolate, console_object, "warn", &Warn);
+ BindLogMethod(isolate, console_object, "error", &Error);
+ return handle_scope.Escape(console_object);
+}
+
+} // namespace console
+} // namespace extensions
diff --git a/chromium/extensions/renderer/console.h b/chromium/extensions/renderer/console.h
new file mode 100644
index 00000000000..79d338e773a
--- /dev/null
+++ b/chromium/extensions/renderer/console.h
@@ -0,0 +1,46 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_CONSOLE_H_
+#define EXTENSIONS_RENDERER_CONSOLE_H_
+
+#include <string>
+
+#include "content/public/common/console_message_level.h"
+#include "v8/include/v8.h"
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+
+// Utility for logging messages to RenderFrames.
+namespace console {
+
+// Adds |message| to the console of |render_frame|. If |render_frame| is null,
+// LOG()s the message instead.
+void Debug(content::RenderFrame* render_frame, const std::string& message);
+void Log(content::RenderFrame* render_frame, const std::string& message);
+void Warn(content::RenderFrame* render_frame, const std::string& message);
+void Error(content::RenderFrame* render_frame, const std::string& message);
+
+// Logs an Error then crashes the current process.
+void Fatal(content::RenderFrame* render_frame, const std::string& message);
+
+void AddMessage(content::RenderFrame* render_frame,
+ content::ConsoleMessageLevel level,
+ const std::string& message);
+
+// Returns a new v8::Object with each standard log method (Debug/Log/Warn/Error)
+// bound to respective debug/log/warn/error methods. This is a direct drop-in
+// replacement for the standard devtools console.* methods usually accessible
+// from JS.
+v8::Local<v8::Object> AsV8Object(v8::Isolate* isolate);
+
+} // namespace console
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_CONSOLE_H_
diff --git a/chromium/extensions/renderer/console_apitest.cc b/chromium/extensions/renderer/console_apitest.cc
new file mode 100644
index 00000000000..de6e3b10a95
--- /dev/null
+++ b/chromium/extensions/renderer/console_apitest.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 2014 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.
+
+#include "chrome/browser/extensions/extension_apitest.h"
+
+namespace extensions {
+namespace {
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, UncaughtExceptionLogging) {
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(RunExtensionTest("uncaught_exception_logging")) << message_;
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/content_watcher.cc b/chromium/extensions/renderer/content_watcher.cc
new file mode 100644
index 00000000000..8c99e11364c
--- /dev/null
+++ b/chromium/extensions/renderer/content_watcher.cc
@@ -0,0 +1,125 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/content_watcher.h"
+
+#include <stddef.h>
+
+#include "content/public/renderer/render_view.h"
+#include "content/public/renderer/render_view_visitor.h"
+#include "extensions/common/extension_messages.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebElement.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+namespace extensions {
+
+using blink::WebString;
+using blink::WebVector;
+using blink::WebView;
+
+ContentWatcher::ContentWatcher() {}
+ContentWatcher::~ContentWatcher() {}
+
+void ContentWatcher::OnWatchPages(
+ const std::vector<std::string>& new_css_selectors_utf8) {
+ blink::WebVector<blink::WebString> new_css_selectors(
+ new_css_selectors_utf8.size());
+ bool changed = new_css_selectors.size() != css_selectors_.size();
+ for (size_t i = 0; i < new_css_selectors.size(); ++i) {
+ new_css_selectors[i] =
+ blink::WebString::fromUTF8(new_css_selectors_utf8[i]);
+ if (!changed && new_css_selectors[i] != css_selectors_[i])
+ changed = true;
+ }
+
+ if (!changed)
+ return;
+
+ css_selectors_.swap(new_css_selectors);
+
+ // Tell each frame's document about the new set of watched selectors. These
+ // will trigger calls to DidMatchCSS after Blink has a chance to apply the new
+ // style, which will in turn notify the browser about the changes.
+ struct WatchSelectors : public content::RenderViewVisitor {
+ explicit WatchSelectors(const WebVector<WebString>& css_selectors)
+ : css_selectors_(css_selectors) {}
+
+ bool Visit(content::RenderView* view) override {
+ // TODO(dcheng): This should be rewritten to be frame-oriented. It
+ // probably breaks declarative content for OOPI.
+ for (blink::WebFrame* frame = view->GetWebView()->mainFrame(); frame;
+ frame = frame->traverseNext(/*wrap=*/false)) {
+ if (frame->isWebRemoteFrame())
+ continue;
+ frame->toWebLocalFrame()->document().watchCSSSelectors(css_selectors_);
+ }
+
+ return true; // Continue visiting.
+ }
+
+ const WebVector<WebString>& css_selectors_;
+ };
+ WatchSelectors visitor(css_selectors_);
+ content::RenderView::ForEach(&visitor);
+}
+
+void ContentWatcher::DidCreateDocumentElement(blink::WebLocalFrame* frame) {
+ frame->document().watchCSSSelectors(css_selectors_);
+}
+
+void ContentWatcher::DidMatchCSS(
+ blink::WebLocalFrame* frame,
+ const WebVector<WebString>& newly_matching_selectors,
+ const WebVector<WebString>& stopped_matching_selectors) {
+ std::set<std::string>& frame_selectors = matching_selectors_[frame];
+ for (size_t i = 0; i < stopped_matching_selectors.size(); ++i)
+ frame_selectors.erase(stopped_matching_selectors[i].utf8());
+ for (size_t i = 0; i < newly_matching_selectors.size(); ++i)
+ frame_selectors.insert(newly_matching_selectors[i].utf8());
+
+ if (frame_selectors.empty())
+ matching_selectors_.erase(frame);
+
+ NotifyBrowserOfChange(frame);
+}
+
+void ContentWatcher::NotifyBrowserOfChange(
+ blink::WebLocalFrame* changed_frame) const {
+ blink::WebFrame* const top_frame = changed_frame->top();
+ const blink::WebSecurityOrigin top_origin = top_frame->getSecurityOrigin();
+ // Want to aggregate matched selectors from all frames where an
+ // extension with access to top_origin could run on the frame.
+ if (!top_origin.canAccess(changed_frame->document().getSecurityOrigin())) {
+ // If the changed frame can't be accessed by the top frame, then
+ // no change in it could affect the set of selectors we'd send back.
+ return;
+ }
+
+ std::set<base::StringPiece> transitive_selectors;
+ for (blink::WebFrame* frame = top_frame; frame;
+ frame = frame->traverseNext(/*wrap=*/false)) {
+ if (top_origin.canAccess(frame->getSecurityOrigin())) {
+ std::map<blink::WebFrame*, std::set<std::string> >::const_iterator
+ frame_selectors = matching_selectors_.find(frame);
+ if (frame_selectors != matching_selectors_.end()) {
+ transitive_selectors.insert(frame_selectors->second.begin(),
+ frame_selectors->second.end());
+ }
+ }
+ }
+ std::vector<std::string> selector_strings;
+ for (std::set<base::StringPiece>::const_iterator it =
+ transitive_selectors.begin();
+ it != transitive_selectors.end();
+ ++it)
+ selector_strings.push_back(it->as_string());
+ content::RenderView* view =
+ content::RenderView::FromWebView(top_frame->view());
+ view->Send(new ExtensionHostMsg_OnWatchedPageChange(view->GetRoutingID(),
+ selector_strings));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/content_watcher.h b/chromium/extensions/renderer/content_watcher.h
new file mode 100644
index 00000000000..4480ce99cf7
--- /dev/null
+++ b/chromium/extensions/renderer/content_watcher.h
@@ -0,0 +1,73 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_CONTENT_WATCHER_H_
+#define EXTENSIONS_RENDERER_CONTENT_WATCHER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "third_party/WebKit/public/platform/WebVector.h"
+
+namespace blink {
+class WebFrame;
+class WebLocalFrame;
+class WebString;
+}
+
+namespace extensions {
+class Dispatcher;
+class Extension;
+class NativeHandler;
+
+// Watches the content of WebFrames to notify extensions when they match various
+// patterns. This class tracks the set of relevant patterns (set by
+// ExtensionMsg_WatchPages) and the set that match on each WebFrame, and sends a
+// ExtensionHostMsg_OnWatchedPageChange whenever a RenderView's set changes.
+//
+// There's one ContentWatcher per Dispatcher rather than per RenderView because
+// WebFrames can move between RenderViews through adoptNode.
+// TODO(devlin): This class isn't OOPI-safe.
+class ContentWatcher {
+ public:
+ ContentWatcher();
+ ~ContentWatcher();
+
+ // Handler for ExtensionMsg_WatchPages.
+ void OnWatchPages(const std::vector<std::string>& css_selectors);
+
+ // Uses WebDocument::watchCSSSelectors to watch the selectors in
+ // css_selectors_ and get a callback into DidMatchCSS() whenever the set of
+ // matching selectors in |frame| changes.
+ void DidCreateDocumentElement(blink::WebLocalFrame* frame);
+
+ // Records that |newly_matching_selectors| have started matching on |*frame|,
+ // and |stopped_matching_selectors| have stopped matching.
+ void DidMatchCSS(
+ blink::WebLocalFrame* frame,
+ const blink::WebVector<blink::WebString>& newly_matching_selectors,
+ const blink::WebVector<blink::WebString>& stopped_matching_selectors);
+
+ private:
+ // Given that we saw a change in the CSS selectors that |changed_frame|
+ // matched, tell the browser about the new set of matching selectors in its
+ // top-level page. We filter this so that if an extension were to be granted
+ // activeTab permission on that top-level page, we only send CSS selectors for
+ // frames that it could run on.
+ void NotifyBrowserOfChange(blink::WebLocalFrame* changed_frame) const;
+
+ // If any of these selectors match on a page, we need to send an
+ // ExtensionHostMsg_OnWatchedPageChange back to the browser.
+ blink::WebVector<blink::WebString> css_selectors_;
+
+ // Maps live WebFrames to the set of CSS selectors they match. Blink sends
+ // back diffs, which we apply to these sets.
+ std::map<blink::WebFrame*, std::set<std::string> > matching_selectors_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_CONTENT_WATCHER_H_
diff --git a/chromium/extensions/renderer/context_menus_custom_bindings.cc b/chromium/extensions/renderer/context_menus_custom_bindings.cc
new file mode 100644
index 00000000000..40f4705ebf3
--- /dev/null
+++ b/chromium/extensions/renderer/context_menus_custom_bindings.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/context_menus_custom_bindings.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "v8/include/v8.h"
+
+namespace {
+
+void GetNextContextMenuId(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ int context_menu_id = -1;
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_GenerateUniqueID(&context_menu_id));
+ args.GetReturnValue().Set(static_cast<int32_t>(context_menu_id));
+}
+
+} // namespace
+
+namespace extensions {
+
+ContextMenusCustomBindings::ContextMenusCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("GetNextContextMenuId", base::Bind(&GetNextContextMenuId));
+}
+
+} // extensions
diff --git a/chromium/extensions/renderer/context_menus_custom_bindings.h b/chromium/extensions/renderer/context_menus_custom_bindings.h
new file mode 100644
index 00000000000..7c625b48a83
--- /dev/null
+++ b/chromium/extensions/renderer/context_menus_custom_bindings.h
@@ -0,0 +1,21 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_CONTEXT_MENUS_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_CONTEXT_MENUS_CUSTOM_BINDINGS_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Implements custom bindings for the contextMenus API.
+class ContextMenusCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ ContextMenusCustomBindings(ScriptContext* context);
+};
+
+} // extensions
+
+#endif // EXTENSIONS_RENDERER_CONTEXT_MENUS_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/css_native_handler.cc b/chromium/extensions/renderer/css_native_handler.cc
new file mode 100644
index 00000000000..d7cdc39f164
--- /dev/null
+++ b/chromium/extensions/renderer/css_native_handler.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/css_native_handler.h"
+
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/web/WebSelector.h"
+
+namespace extensions {
+
+using blink::WebString;
+
+CssNativeHandler::CssNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("CanonicalizeCompoundSelector",
+ base::Bind(&CssNativeHandler::CanonicalizeCompoundSelector,
+ base::Unretained(this)));
+}
+
+void CssNativeHandler::CanonicalizeCompoundSelector(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsString());
+ std::string input_selector = *v8::String::Utf8Value(args[0]);
+ // TODO(esprehn): This API shouldn't exist, the extension code should be
+ // moved into blink.
+ WebString output_selector = blink::canonicalizeSelector(
+ WebString::fromUTF8(input_selector), blink::WebSelectorTypeCompound);
+ args.GetReturnValue().Set(v8_helpers::ToV8StringUnsafe(
+ args.GetIsolate(), output_selector.utf8().c_str()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/css_native_handler.h b/chromium/extensions/renderer/css_native_handler.h
new file mode 100644
index 00000000000..c3d1bc34649
--- /dev/null
+++ b/chromium/extensions/renderer/css_native_handler.h
@@ -0,0 +1,28 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_CSS_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_CSS_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+class CssNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit CssNativeHandler(ScriptContext* context);
+
+ private:
+ // Expects one string argument that's a comma-separated list of compound CSS
+ // selectors (http://dev.w3.org/csswg/selectors4/#compound), and returns its
+ // Blink-canonicalized form. If the selector is invalid, returns an empty
+ // string.
+ void CanonicalizeCompoundSelector(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_CSS_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/dispatcher.cc b/chromium/extensions/renderer/dispatcher.cc
new file mode 100644
index 00000000000..60a4a11bfec
--- /dev/null
+++ b/chromium/extensions/renderer/dispatcher.cc
@@ -0,0 +1,1629 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/dispatcher.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/debug/alias.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "content/grit/content_resources.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/common/browser_plugin_guest_mode.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/features/behavior_feature.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/manifest_handlers/content_capabilities_handler.h"
+#include "extensions/common/manifest_handlers/externally_connectable.h"
+#include "extensions/common/manifest_handlers/options_page_info.h"
+#include "extensions/common/message_bundle.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/switches.h"
+#include "extensions/common/view_type.h"
+#include "extensions/renderer/api_activity_logger.h"
+#include "extensions/renderer/api_definitions_natives.h"
+#include "extensions/renderer/app_window_custom_bindings.h"
+#include "extensions/renderer/binding_generating_native_handler.h"
+#include "extensions/renderer/blob_native_handler.h"
+#include "extensions/renderer/content_watcher.h"
+#include "extensions/renderer/context_menus_custom_bindings.h"
+#include "extensions/renderer/css_native_handler.h"
+#include "extensions/renderer/dispatcher_delegate.h"
+#include "extensions/renderer/display_source_custom_bindings.h"
+#include "extensions/renderer/document_custom_bindings.h"
+#include "extensions/renderer/dom_activity_logger.h"
+#include "extensions/renderer/event_bindings.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/extension_helper.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+#include "extensions/renderer/file_system_natives.h"
+#include "extensions/renderer/guest_view/guest_view_internal_custom_bindings.h"
+#include "extensions/renderer/i18n_custom_bindings.h"
+#include "extensions/renderer/id_generator_custom_bindings.h"
+#include "extensions/renderer/lazy_background_page_native_handler.h"
+#include "extensions/renderer/logging_native_handler.h"
+#include "extensions/renderer/messaging_bindings.h"
+#include "extensions/renderer/module_system.h"
+#include "extensions/renderer/print_native_handler.h"
+#include "extensions/renderer/process_info_native_handler.h"
+#include "extensions/renderer/render_frame_observer_natives.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/request_sender.h"
+#include "extensions/renderer/runtime_custom_bindings.h"
+#include "extensions/renderer/safe_builtins.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/script_injection_manager.h"
+#include "extensions/renderer/send_request_natives.h"
+#include "extensions/renderer/set_icon_natives.h"
+#include "extensions/renderer/static_v8_external_one_byte_string_resource.h"
+#include "extensions/renderer/test_features_native_handler.h"
+#include "extensions/renderer/test_native_handler.h"
+#include "extensions/renderer/user_gestures_native_handler.h"
+#include "extensions/renderer/utils_native_handler.h"
+#include "extensions/renderer/v8_context_native_handler.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "extensions/renderer/wake_event_page.h"
+#include "extensions/renderer/worker_script_context_set.h"
+#include "grit/extensions_renderer_resources.h"
+#include "mojo/public/js/constants.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURLRequest.h"
+#include "third_party/WebKit/public/web/WebCustomElement.h"
+#include "third_party/WebKit/public/web/WebDataSource.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebRuntimeFeatures.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebSecurityPolicy.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "ui/base/layout.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "v8/include/v8.h"
+
+using base::UserMetricsAction;
+using blink::WebDataSource;
+using blink::WebDocument;
+using blink::WebScopedUserGesture;
+using blink::WebSecurityPolicy;
+using blink::WebString;
+using blink::WebVector;
+using blink::WebView;
+using content::RenderThread;
+
+namespace extensions {
+
+namespace {
+
+static const int64_t kInitialExtensionIdleHandlerDelayMs = 5 * 1000;
+static const int64_t kMaxExtensionIdleHandlerDelayMs = 5 * 60 * 1000;
+static const char kEventDispatchFunction[] = "dispatchEvent";
+static const char kOnSuspendEvent[] = "runtime.onSuspend";
+static const char kOnSuspendCanceledEvent[] = "runtime.onSuspendCanceled";
+
+void CrashOnException(const v8::TryCatch& trycatch) {
+ NOTREACHED();
+};
+
+// Returns the global value for "chrome" from |context|. If one doesn't exist
+// creates a new object for it.
+//
+// Note that this isn't necessarily an object, since webpages can write, for
+// example, "window.chrome = true".
+v8::Local<v8::Value> GetOrCreateChrome(ScriptContext* context) {
+ v8::Local<v8::String> chrome_string(
+ v8::String::NewFromUtf8(context->isolate(), "chrome"));
+ v8::Local<v8::Object> global(context->v8_context()->Global());
+ v8::Local<v8::Value> chrome(global->Get(chrome_string));
+ if (chrome->IsUndefined()) {
+ chrome = v8::Object::New(context->isolate());
+ global->Set(chrome_string, chrome);
+ }
+ return chrome;
+}
+
+// Returns |value| cast to an object if possible, else an empty handle.
+v8::Local<v8::Object> AsObjectOrEmpty(v8::Local<v8::Value> value) {
+ return value->IsObject() ? value.As<v8::Object>() : v8::Local<v8::Object>();
+}
+
+// Calls a method |method_name| in a module |module_name| belonging to the
+// module system from |context|. Intended as a callback target from
+// ScriptContextSet::ForEach.
+void CallModuleMethod(const std::string& module_name,
+ const std::string& method_name,
+ const base::ListValue* args,
+ ScriptContext* context) {
+ v8::HandleScope handle_scope(context->isolate());
+ v8::Context::Scope context_scope(context->v8_context());
+
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+
+ std::vector<v8::Local<v8::Value>> arguments;
+ for (base::ListValue::const_iterator it = args->begin(); it != args->end();
+ ++it) {
+ arguments.push_back(converter->ToV8Value(*it, context->v8_context()));
+ }
+
+ context->module_system()->CallModuleMethod(
+ module_name, method_name, &arguments);
+}
+
+// This handles the "chrome." root API object in script contexts.
+class ChromeNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit ChromeNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetChrome",
+ base::Bind(&ChromeNativeHandler::GetChrome, base::Unretained(this)));
+ }
+
+ void GetChrome(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(GetOrCreateChrome(context()));
+ }
+};
+
+base::LazyInstance<WorkerScriptContextSet> g_worker_script_context_set =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// Note that we can't use Blink public APIs in the constructor becase Blink
+// is not initialized at the point we create Dispatcher.
+Dispatcher::Dispatcher(DispatcherDelegate* delegate)
+ : delegate_(delegate),
+ content_watcher_(new ContentWatcher()),
+ source_map_(&ResourceBundle::GetSharedInstance()),
+ v8_schema_registry_(new V8SchemaRegistry),
+ user_script_set_manager_observer_(this),
+ webrequest_used_(false) {
+ const base::CommandLine& command_line =
+ *(base::CommandLine::ForCurrentProcess());
+ set_idle_notifications_ =
+ command_line.HasSwitch(switches::kExtensionProcess) ||
+ command_line.HasSwitch(::switches::kSingleProcess);
+
+ if (set_idle_notifications_) {
+ RenderThread::Get()->SetIdleNotificationDelayInMs(
+ kInitialExtensionIdleHandlerDelayMs);
+ }
+
+ script_context_set_.reset(new ScriptContextSet(&active_extension_ids_));
+ user_script_set_manager_.reset(new UserScriptSetManager());
+ script_injection_manager_.reset(
+ new ScriptInjectionManager(user_script_set_manager_.get()));
+ user_script_set_manager_observer_.Add(user_script_set_manager_.get());
+ request_sender_.reset(new RequestSender(this));
+ PopulateSourceMap();
+ WakeEventPage::Get()->Init(RenderThread::Get());
+
+ RenderThread::Get()->RegisterExtension(SafeBuiltins::CreateV8Extension());
+
+ // WebSecurityPolicy whitelists. They should be registered for both
+ // chrome-extension: and chrome-extension-resource.
+ using RegisterFunction = void (*)(const WebString&);
+ RegisterFunction register_functions[] = {
+ // Treat as secure because communication with them is entirely in the
+ // browser, so there is no danger of manipulation or eavesdropping on
+ // communication with them by third parties.
+ WebSecurityPolicy::registerURLSchemeAsSecure,
+ // As far as Blink is concerned, they should be allowed to receive CORS
+ // requests. At the Extensions layer, requests will actually be blocked
+ // unless overridden by the web_accessible_resources manifest key.
+ // TODO(kalman): See what happens with a service worker.
+ WebSecurityPolicy::registerURLSchemeAsCORSEnabled,
+ // Resources should bypass Content Security Policy checks when included in
+ // protected resources. TODO(kalman): What are "protected resources"?
+ WebSecurityPolicy::registerURLSchemeAsBypassingContentSecurityPolicy,
+ // Extension resources are HTTP-like and safe to expose to the fetch API.
+ // The rules for the fetch API are consistent with XHR.
+ WebSecurityPolicy::registerURLSchemeAsSupportingFetchAPI,
+ // Extension resources, when loaded as the top-level document, should
+ // bypass Blink's strict first-party origin checks.
+ WebSecurityPolicy::registerURLSchemeAsFirstPartyWhenTopLevel,
+ };
+
+ WebString extension_scheme(base::ASCIIToUTF16(kExtensionScheme));
+ WebString extension_resource_scheme(base::ASCIIToUTF16(
+ kExtensionResourceScheme));
+ for (RegisterFunction func : register_functions) {
+ func(extension_scheme);
+ func(extension_resource_scheme);
+ }
+
+ // For extensions, we want to ensure we call the IdleHandler every so often,
+ // even if the extension keeps up activity.
+ if (set_idle_notifications_) {
+ forced_idle_timer_.reset(new base::RepeatingTimer);
+ forced_idle_timer_->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kMaxExtensionIdleHandlerDelayMs),
+ RenderThread::Get(),
+ &RenderThread::IdleHandler);
+ }
+
+ // Initialize host permissions for any extensions that were activated before
+ // WebKit was initialized.
+ for (const std::string& extension_id : active_extension_ids_) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ CHECK(extension);
+ InitOriginPermissions(extension);
+ }
+
+ EnableCustomElementWhiteList();
+}
+
+Dispatcher::~Dispatcher() {
+}
+
+void Dispatcher::OnRenderFrameCreated(content::RenderFrame* render_frame) {
+ script_injection_manager_->OnRenderFrameCreated(render_frame);
+}
+
+bool Dispatcher::IsExtensionActive(const std::string& extension_id) const {
+ bool is_active =
+ active_extension_ids_.find(extension_id) != active_extension_ids_.end();
+ if (is_active)
+ CHECK(RendererExtensionRegistry::Get()->Contains(extension_id));
+ return is_active;
+}
+
+void Dispatcher::DidCreateScriptContext(
+ blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& v8_context,
+ int extension_group,
+ int world_id) {
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+
+ ScriptContext* context = script_context_set_->Register(
+ frame, v8_context, extension_group, world_id);
+
+ // Initialize origin permissions for content scripts, which can't be
+ // initialized in |OnActivateExtension|.
+ if (context->context_type() == Feature::CONTENT_SCRIPT_CONTEXT)
+ InitOriginPermissions(context->extension());
+
+ {
+ scoped_ptr<ModuleSystem> module_system(
+ new ModuleSystem(context, &source_map_));
+ context->set_module_system(std::move(module_system));
+ }
+ ModuleSystem* module_system = context->module_system();
+
+ // Enable natives in startup.
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system);
+
+ RegisterNativeHandlers(module_system, context);
+
+ // chrome.Event is part of the public API (although undocumented). Make it
+ // lazily evalulate to Event from event_bindings.js. For extensions only
+ // though, not all webpages!
+ if (context->extension()) {
+ v8::Local<v8::Object> chrome = AsObjectOrEmpty(GetOrCreateChrome(context));
+ if (!chrome.IsEmpty())
+ module_system->SetLazyField(chrome, "Event", kEventBindings, "Event");
+ }
+
+ UpdateBindingsForContext(context);
+
+ bool is_within_platform_app = IsWithinPlatformApp();
+ // Inject custom JS into the platform app context.
+ if (is_within_platform_app) {
+ module_system->Require("platformApp");
+ }
+
+ RequireGuestViewModules(context);
+ delegate_->RequireAdditionalModules(context, is_within_platform_app);
+
+ const base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
+ switch (context->context_type()) {
+ case Feature::UNSPECIFIED_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_Unspecified",
+ elapsed);
+ break;
+ case Feature::BLESSED_EXTENSION_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_Blessed", elapsed);
+ break;
+ case Feature::UNBLESSED_EXTENSION_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_Unblessed",
+ elapsed);
+ break;
+ case Feature::CONTENT_SCRIPT_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_ContentScript",
+ elapsed);
+ break;
+ case Feature::WEB_PAGE_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_WebPage", elapsed);
+ break;
+ case Feature::BLESSED_WEB_PAGE_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_BlessedWebPage",
+ elapsed);
+ break;
+ case Feature::WEBUI_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_WebUI", elapsed);
+ break;
+ case Feature::SERVICE_WORKER_CONTEXT:
+ // Handled in DidInitializeServiceWorkerContextOnWorkerThread().
+ NOTREACHED();
+ break;
+ }
+
+ VLOG(1) << "Num tracked contexts: " << script_context_set_->size();
+}
+
+// static
+void Dispatcher::DidInitializeServiceWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> v8_context,
+ const GURL& url) {
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+
+ if (!url.SchemeIs(kExtensionScheme) &&
+ !url.SchemeIs(kExtensionResourceScheme)) {
+ // Early-out if this isn't a chrome-extension:// or resource scheme,
+ // because looking up the extension registry is unnecessary if it's not.
+ // Checking this will also skip over hosted apps, which is the desired
+ // behavior - hosted app service workers are not our concern.
+ return;
+ }
+
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(url);
+
+ if (!extension) {
+ // TODO(kalman): This is no good. Instead we need to either:
+ //
+ // - Hold onto the v8::Context and create the ScriptContext and install
+ // our bindings when this extension is loaded.
+ // - Deal with there being an extension ID (url.host()) but no
+ // extension associated with it, then document that getBackgroundClient
+ // may fail if the extension hasn't loaded yet.
+ //
+ // The former is safer, but is unfriendly to caching (e.g. session restore).
+ // It seems to contradict the service worker idiom.
+ //
+ // The latter is friendly to caching, but running extension code without an
+ // installed extension makes me nervous, and means that we won't be able to
+ // expose arbitrary (i.e. capability-checked) extension APIs to service
+ // workers. We will probably need to relax some assertions - we just need
+ // to find them.
+ //
+ // Perhaps this could be solved with our own event on the service worker
+ // saying that an extension is ready, and documenting that extension APIs
+ // won't work before that event has fired?
+ return;
+ }
+
+ ScriptContext* context = new ScriptContext(
+ v8_context, nullptr, extension, Feature::SERVICE_WORKER_CONTEXT,
+ extension, Feature::SERVICE_WORKER_CONTEXT);
+ context->set_url(url);
+
+ g_worker_script_context_set.Get().Insert(make_scoped_ptr(context));
+
+ v8::Isolate* isolate = context->isolate();
+
+ // Fetch the source code for service_worker_bindings.js.
+ base::StringPiece script_resource =
+ ResourceBundle::GetSharedInstance().GetRawDataResource(
+ IDR_SERVICE_WORKER_BINDINGS_JS);
+ v8::Local<v8::String> script = v8::String::NewExternal(
+ isolate, new StaticV8ExternalOneByteStringResource(script_resource));
+
+ // Run service_worker.js to get the main function.
+ v8::Local<v8::Function> main_function;
+ {
+ v8::Local<v8::Value> result = context->RunScript(
+ v8_helpers::ToV8StringUnsafe(isolate, "service_worker"), script,
+ base::Bind(&CrashOnException));
+ CHECK(result->IsFunction());
+ main_function = result.As<v8::Function>();
+ }
+
+ // Expose CHECK/DCHECK/NOTREACHED to the main function with a
+ // LoggingNativeHandler. Admire the neat base::Bind trick to both Invalidate
+ // and delete the native handler.
+ LoggingNativeHandler* logging = new LoggingNativeHandler(context);
+ context->AddInvalidationObserver(
+ base::Bind(&NativeHandler::Invalidate, base::Owned(logging)));
+
+ // Execute the main function with its dependencies passed in as arguments.
+ v8::Local<v8::Value> args[] = {
+ // The extension's background URL.
+ v8_helpers::ToV8StringUnsafe(
+ isolate, BackgroundInfo::GetBackgroundURL(extension).spec()),
+ // The wake-event-page native function.
+ WakeEventPage::Get()->GetForContext(context),
+ // The logging module.
+ logging->NewInstance(),
+ };
+ context->CallFunction(main_function, arraysize(args), args);
+
+ const base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
+ UMA_HISTOGRAM_TIMES(
+ "Extensions.DidInitializeServiceWorkerContextOnWorkerThread", elapsed);
+}
+
+void Dispatcher::WillReleaseScriptContext(
+ blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& v8_context,
+ int world_id) {
+ ScriptContext* context = script_context_set_->GetByV8Context(v8_context);
+ if (!context)
+ return;
+
+ // TODO(kalman): Make |request_sender| use |context->AddInvalidationObserver|.
+ // In fact |request_sender_| should really be owned by ScriptContext.
+ request_sender_->InvalidateSource(context);
+
+ script_context_set_->Remove(context);
+ VLOG(1) << "Num tracked contexts: " << script_context_set_->size();
+}
+
+// static
+void Dispatcher::WillDestroyServiceWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> v8_context,
+ const GURL& url) {
+ if (url.SchemeIs(kExtensionScheme) ||
+ url.SchemeIs(kExtensionResourceScheme)) {
+ // See comment in DidInitializeServiceWorkerContextOnWorkerThread.
+ g_worker_script_context_set.Get().Remove(v8_context, url);
+ }
+}
+
+void Dispatcher::DidCreateDocumentElement(blink::WebLocalFrame* frame) {
+ // Note: use GetEffectiveDocumentURL not just frame->document()->url()
+ // so that this also injects the stylesheet on about:blank frames that
+ // are hosted in the extension process.
+ GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
+ frame, frame->document().url(), true /* match_about_blank */);
+
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(
+ effective_document_url);
+
+ if (extension &&
+ (extension->is_extension() || extension->is_platform_app())) {
+ int resource_id = extension->is_platform_app() ? IDR_PLATFORM_APP_CSS
+ : IDR_EXTENSION_FONTS_CSS;
+ std::string stylesheet = ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(resource_id)
+ .as_string();
+ base::ReplaceFirstSubstringAfterOffset(
+ &stylesheet, 0, "$FONTFAMILY", system_font_family_);
+ base::ReplaceFirstSubstringAfterOffset(
+ &stylesheet, 0, "$FONTSIZE", system_font_size_);
+
+ // Blink doesn't let us define an additional user agent stylesheet, so
+ // we insert the default platform app or extension stylesheet into all
+ // documents that are loaded in each app or extension.
+ frame->document().insertStyleSheet(WebString::fromUTF8(stylesheet));
+ }
+
+ // If this is an extension options page, and the extension has opted into
+ // using Chrome styles, then insert the Chrome extension stylesheet.
+ if (extension && extension->is_extension() &&
+ OptionsPageInfo::ShouldUseChromeStyle(extension) &&
+ effective_document_url == OptionsPageInfo::GetOptionsPage(extension)) {
+ frame->document().insertStyleSheet(
+ WebString::fromUTF8(ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(IDR_EXTENSION_CSS)
+ .as_string()));
+ }
+
+ // In testing, the document lifetime events can happen after the render
+ // process shutdown event.
+ // See: http://crbug.com/21508 and http://crbug.com/500851
+ if (content_watcher_) {
+ content_watcher_->DidCreateDocumentElement(frame);
+ }
+}
+
+void Dispatcher::RunScriptsAtDocumentStart(content::RenderFrame* render_frame) {
+ ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
+ if (!frame_helper)
+ return; // The frame is invisible to extensions.
+
+ frame_helper->RunScriptsAtDocumentStart();
+ // |frame_helper| and |render_frame| might be dead by now.
+}
+
+void Dispatcher::RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) {
+ ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
+ if (!frame_helper)
+ return; // The frame is invisible to extensions.
+
+ frame_helper->RunScriptsAtDocumentEnd();
+ // |frame_helper| and |render_frame| might be dead by now.
+}
+
+void Dispatcher::OnExtensionResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) {
+ request_sender_->HandleResponse(request_id, success, response, error);
+}
+
+void Dispatcher::DispatchEvent(const std::string& extension_id,
+ const std::string& event_name) const {
+ base::ListValue args;
+ args.Set(0, new base::StringValue(event_name));
+ args.Set(1, new base::ListValue());
+
+ // Needed for Windows compilation, since kEventBindings is declared extern.
+ const char* local_event_bindings = kEventBindings;
+ script_context_set_->ForEach(
+ extension_id, base::Bind(&CallModuleMethod, local_event_bindings,
+ kEventDispatchFunction, &args));
+}
+
+void Dispatcher::InvokeModuleSystemMethod(content::RenderFrame* render_frame,
+ const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture) {
+ scoped_ptr<WebScopedUserGesture> web_user_gesture;
+ if (user_gesture)
+ web_user_gesture.reset(new WebScopedUserGesture);
+
+ script_context_set_->ForEach(
+ extension_id, render_frame,
+ base::Bind(&CallModuleMethod, module_name, function_name, &args));
+
+ // Reset the idle handler each time there's any activity like event or message
+ // dispatch, for which Invoke is the chokepoint.
+ if (set_idle_notifications_) {
+ RenderThread::Get()->ScheduleIdleHandler(
+ kInitialExtensionIdleHandlerDelayMs);
+ }
+
+ // Tell the browser process when an event has been dispatched with a lazy
+ // background page active.
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (extension && BackgroundInfo::HasLazyBackgroundPage(extension) &&
+ module_name == kEventBindings &&
+ function_name == kEventDispatchFunction) {
+ content::RenderFrame* background_frame =
+ ExtensionFrameHelper::GetBackgroundPageFrame(extension_id);
+ if (background_frame) {
+ int message_id;
+ args.GetInteger(3, &message_id);
+ background_frame->Send(new ExtensionHostMsg_EventAck(
+ background_frame->GetRoutingID(), message_id));
+ }
+ }
+}
+
+void Dispatcher::ClearPortData(int port_id) {
+ // Only the target port side has entries in |port_to_tab_id_map_|. If
+ // |port_id| is a source port, std::map::erase() will just silently fail
+ // here as a no-op.
+ port_to_tab_id_map_.erase(port_id);
+}
+
+// static
+std::vector<std::pair<std::string, int> > Dispatcher::GetJsResources() {
+ std::vector<std::pair<std::string, int> > resources;
+
+ // Libraries.
+ resources.push_back(std::make_pair("appView", IDR_APP_VIEW_JS));
+ resources.push_back(std::make_pair("entryIdManager", IDR_ENTRY_ID_MANAGER));
+ resources.push_back(std::make_pair(kEventBindings, IDR_EVENT_BINDINGS_JS));
+ resources.push_back(std::make_pair("extensionOptions",
+ IDR_EXTENSION_OPTIONS_JS));
+ resources.push_back(std::make_pair("extensionOptionsAttributes",
+ IDR_EXTENSION_OPTIONS_ATTRIBUTES_JS));
+ resources.push_back(std::make_pair("extensionOptionsConstants",
+ IDR_EXTENSION_OPTIONS_CONSTANTS_JS));
+ resources.push_back(std::make_pair("extensionOptionsEvents",
+ IDR_EXTENSION_OPTIONS_EVENTS_JS));
+ resources.push_back(std::make_pair("extensionView", IDR_EXTENSION_VIEW_JS));
+ resources.push_back(std::make_pair("extensionViewApiMethods",
+ IDR_EXTENSION_VIEW_API_METHODS_JS));
+ resources.push_back(std::make_pair("extensionViewAttributes",
+ IDR_EXTENSION_VIEW_ATTRIBUTES_JS));
+ resources.push_back(std::make_pair("extensionViewConstants",
+ IDR_EXTENSION_VIEW_CONSTANTS_JS));
+ resources.push_back(std::make_pair("extensionViewEvents",
+ IDR_EXTENSION_VIEW_EVENTS_JS));
+ resources.push_back(std::make_pair(
+ "extensionViewInternal", IDR_EXTENSION_VIEW_INTERNAL_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("guestView", IDR_GUEST_VIEW_JS));
+ resources.push_back(std::make_pair("guestViewAttributes",
+ IDR_GUEST_VIEW_ATTRIBUTES_JS));
+ resources.push_back(std::make_pair("guestViewContainer",
+ IDR_GUEST_VIEW_CONTAINER_JS));
+ resources.push_back(std::make_pair("guestViewDeny", IDR_GUEST_VIEW_DENY_JS));
+ resources.push_back(std::make_pair("guestViewEvents",
+ IDR_GUEST_VIEW_EVENTS_JS));
+
+ if (content::BrowserPluginGuestMode::UseCrossProcessFramesForGuests()) {
+ resources.push_back(std::make_pair("guestViewIframe",
+ IDR_GUEST_VIEW_IFRAME_JS));
+ resources.push_back(std::make_pair("guestViewIframeContainer",
+ IDR_GUEST_VIEW_IFRAME_CONTAINER_JS));
+ }
+
+ resources.push_back(std::make_pair("imageUtil", IDR_IMAGE_UTIL_JS));
+ resources.push_back(std::make_pair("json_schema", IDR_JSON_SCHEMA_JS));
+ resources.push_back(std::make_pair("lastError", IDR_LAST_ERROR_JS));
+ resources.push_back(std::make_pair("messaging", IDR_MESSAGING_JS));
+ resources.push_back(std::make_pair("messaging_utils",
+ IDR_MESSAGING_UTILS_JS));
+ resources.push_back(std::make_pair(kSchemaUtils, IDR_SCHEMA_UTILS_JS));
+ resources.push_back(std::make_pair("sendRequest", IDR_SEND_REQUEST_JS));
+ resources.push_back(std::make_pair("setIcon", IDR_SET_ICON_JS));
+ resources.push_back(std::make_pair("test", IDR_TEST_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("test_environment_specific_bindings",
+ IDR_BROWSER_TEST_ENVIRONMENT_SPECIFIC_BINDINGS_JS));
+ resources.push_back(std::make_pair("uncaught_exception_handler",
+ IDR_UNCAUGHT_EXCEPTION_HANDLER_JS));
+ resources.push_back(std::make_pair("utils", IDR_UTILS_JS));
+ resources.push_back(std::make_pair("webRequest",
+ IDR_WEB_REQUEST_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("webRequestInternal",
+ IDR_WEB_REQUEST_INTERNAL_CUSTOM_BINDINGS_JS));
+ // Note: webView not webview so that this doesn't interfere with the
+ // chrome.webview API bindings.
+ resources.push_back(std::make_pair("webView", IDR_WEB_VIEW_JS));
+ resources.push_back(std::make_pair("webViewActionRequests",
+ IDR_WEB_VIEW_ACTION_REQUESTS_JS));
+ resources.push_back(std::make_pair("webViewApiMethods",
+ IDR_WEB_VIEW_API_METHODS_JS));
+ resources.push_back(std::make_pair("webViewAttributes",
+ IDR_WEB_VIEW_ATTRIBUTES_JS));
+ resources.push_back(std::make_pair("webViewConstants",
+ IDR_WEB_VIEW_CONSTANTS_JS));
+ resources.push_back(std::make_pair("webViewEvents", IDR_WEB_VIEW_EVENTS_JS));
+ resources.push_back(std::make_pair("webViewInternal",
+ IDR_WEB_VIEW_INTERNAL_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("webViewExperimental", IDR_WEB_VIEW_EXPERIMENTAL_JS));
+ resources.push_back(
+ std::make_pair(mojo::kBindingsModuleName, IDR_MOJO_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair(mojo::kBufferModuleName, IDR_MOJO_BUFFER_JS));
+ resources.push_back(
+ std::make_pair(mojo::kCodecModuleName, IDR_MOJO_CODEC_JS));
+ resources.push_back(
+ std::make_pair(mojo::kConnectionModuleName, IDR_MOJO_CONNECTION_JS));
+ resources.push_back(
+ std::make_pair(mojo::kConnectorModuleName, IDR_MOJO_CONNECTOR_JS));
+ resources.push_back(
+ std::make_pair(mojo::kRouterModuleName, IDR_MOJO_ROUTER_JS));
+ resources.push_back(
+ std::make_pair(mojo::kUnicodeModuleName, IDR_MOJO_UNICODE_JS));
+ resources.push_back(
+ std::make_pair(mojo::kValidatorModuleName, IDR_MOJO_VALIDATOR_JS));
+ resources.push_back(std::make_pair("async_waiter", IDR_ASYNC_WAITER_JS));
+ resources.push_back(std::make_pair("data_receiver", IDR_DATA_RECEIVER_JS));
+ resources.push_back(std::make_pair("data_sender", IDR_DATA_SENDER_JS));
+ resources.push_back(std::make_pair("keep_alive", IDR_KEEP_ALIVE_JS));
+ resources.push_back(std::make_pair("extensions/common/mojo/keep_alive.mojom",
+ IDR_KEEP_ALIVE_MOJOM_JS));
+ resources.push_back(std::make_pair("device/serial/data_stream.mojom",
+ IDR_DATA_STREAM_MOJOM_JS));
+ resources.push_back(
+ std::make_pair("device/serial/data_stream_serialization.mojom",
+ IDR_DATA_STREAM_SERIALIZATION_MOJOM_JS));
+ resources.push_back(std::make_pair("stash_client", IDR_STASH_CLIENT_JS));
+ resources.push_back(
+ std::make_pair("extensions/common/mojo/stash.mojom", IDR_STASH_MOJOM_JS));
+
+ // Custom bindings.
+ resources.push_back(
+ std::make_pair("app.runtime", IDR_APP_RUNTIME_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("app.window", IDR_APP_WINDOW_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("declarativeWebRequest",
+ IDR_DECLARATIVE_WEBREQUEST_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("displaySource",
+ IDR_DISPLAY_SOURCE_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("contextMenus", IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("contextMenusHandlers", IDR_CONTEXT_MENUS_HANDLERS_JS));
+ resources.push_back(
+ std::make_pair("extension", IDR_EXTENSION_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("i18n", IDR_I18N_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair(
+ "mimeHandlerPrivate", IDR_MIME_HANDLER_PRIVATE_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("extensions/common/api/mime_handler.mojom",
+ IDR_MIME_HANDLER_MOJOM_JS));
+ resources.push_back(
+ std::make_pair("mojoPrivate", IDR_MOJO_PRIVATE_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("permissions", IDR_PERMISSIONS_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("printerProvider",
+ IDR_PRINTER_PROVIDER_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("runtime", IDR_RUNTIME_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("windowControls", IDR_WINDOW_CONTROLS_JS));
+ resources.push_back(
+ std::make_pair("webViewRequest",
+ IDR_WEB_VIEW_REQUEST_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("binding", IDR_BINDING_JS));
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableMojoSerialService)) {
+ resources.push_back(
+ std::make_pair("serial", IDR_SERIAL_CUSTOM_BINDINGS_JS));
+ }
+ resources.push_back(std::make_pair("serial_service", IDR_SERIAL_SERVICE_JS));
+ resources.push_back(
+ std::make_pair("device/serial/serial.mojom", IDR_SERIAL_MOJOM_JS));
+ resources.push_back(std::make_pair("device/serial/serial_serialization.mojom",
+ IDR_SERIAL_SERIALIZATION_MOJOM_JS));
+
+ // Custom types sources.
+ resources.push_back(std::make_pair("StorageArea", IDR_STORAGE_AREA_JS));
+
+ // Platform app sources that are not API-specific..
+ resources.push_back(std::make_pair("platformApp", IDR_PLATFORM_APP_JS));
+
+#if defined(ENABLE_MEDIA_ROUTER)
+ resources.push_back(
+ std::make_pair("chrome/browser/media/router/mojo/media_router.mojom",
+ IDR_MEDIA_ROUTER_MOJOM_JS));
+ resources.push_back(
+ std::make_pair("media_router_bindings", IDR_MEDIA_ROUTER_BINDINGS_JS));
+#endif // defined(ENABLE_MEDIA_ROUTER)
+
+ return resources;
+}
+
+// NOTE: please use the naming convention "foo_natives" for these.
+// static
+void Dispatcher::RegisterNativeHandlers(ModuleSystem* module_system,
+ ScriptContext* context,
+ Dispatcher* dispatcher,
+ RequestSender* request_sender,
+ V8SchemaRegistry* v8_schema_registry) {
+ module_system->RegisterNativeHandler(
+ "chrome", scoped_ptr<NativeHandler>(new ChromeNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "lazy_background_page",
+ scoped_ptr<NativeHandler>(new LazyBackgroundPageNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "logging", scoped_ptr<NativeHandler>(new LoggingNativeHandler(context)));
+ module_system->RegisterNativeHandler("schema_registry",
+ v8_schema_registry->AsNativeHandler());
+ module_system->RegisterNativeHandler(
+ "print", scoped_ptr<NativeHandler>(new PrintNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "test_features",
+ scoped_ptr<NativeHandler>(new TestFeaturesNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "test_native_handler",
+ scoped_ptr<NativeHandler>(new TestNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "user_gestures",
+ scoped_ptr<NativeHandler>(new UserGesturesNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "utils", scoped_ptr<NativeHandler>(new UtilsNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "v8_context",
+ scoped_ptr<NativeHandler>(new V8ContextNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "event_natives", scoped_ptr<NativeHandler>(new EventBindings(context)));
+ module_system->RegisterNativeHandler(
+ "messaging_natives",
+ scoped_ptr<NativeHandler>(MessagingBindings::Get(dispatcher, context)));
+ module_system->RegisterNativeHandler(
+ "apiDefinitions",
+ scoped_ptr<NativeHandler>(
+ new ApiDefinitionsNatives(dispatcher, context)));
+ module_system->RegisterNativeHandler(
+ "sendRequest",
+ scoped_ptr<NativeHandler>(
+ new SendRequestNatives(request_sender, context)));
+ module_system->RegisterNativeHandler(
+ "setIcon",
+ scoped_ptr<NativeHandler>(new SetIconNatives(context)));
+ module_system->RegisterNativeHandler(
+ "activityLogger",
+ scoped_ptr<NativeHandler>(new APIActivityLogger(context)));
+ module_system->RegisterNativeHandler(
+ "renderFrameObserverNatives",
+ scoped_ptr<NativeHandler>(new RenderFrameObserverNatives(context)));
+
+ // Natives used by multiple APIs.
+ module_system->RegisterNativeHandler(
+ "file_system_natives",
+ scoped_ptr<NativeHandler>(new FileSystemNatives(context)));
+
+ // Custom bindings.
+ module_system->RegisterNativeHandler(
+ "app_window_natives",
+ scoped_ptr<NativeHandler>(new AppWindowCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "blob_natives",
+ scoped_ptr<NativeHandler>(new BlobNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "context_menus",
+ scoped_ptr<NativeHandler>(new ContextMenusCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "css_natives", scoped_ptr<NativeHandler>(new CssNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "document_natives",
+ scoped_ptr<NativeHandler>(new DocumentCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "guest_view_internal",
+ scoped_ptr<NativeHandler>(
+ new GuestViewInternalCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "i18n", scoped_ptr<NativeHandler>(new I18NCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "id_generator",
+ scoped_ptr<NativeHandler>(new IdGeneratorCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "runtime", scoped_ptr<NativeHandler>(new RuntimeCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "display_source",
+ scoped_ptr<NativeHandler>(new DisplaySourceCustomBindings(context)));
+}
+
+bool Dispatcher::OnControlMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(Dispatcher, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ActivateExtension, OnActivateExtension)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_CancelSuspend, OnCancelSuspend)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnDeliverMessage)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnConnect, OnDispatchOnConnect)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect, OnDispatchOnDisconnect)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_Loaded, OnLoaded)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnMessageInvoke)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetChannel, OnSetChannel)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetScriptingWhitelist,
+ OnSetScriptingWhitelist)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetSystemFont, OnSetSystemFont)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetWebViewPartitionID,
+ OnSetWebViewPartitionID)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ShouldSuspend, OnShouldSuspend)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_Suspend, OnSuspend)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_TransferBlobs, OnTransferBlobs)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_Unloaded, OnUnloaded)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateTabSpecificPermissions,
+ OnUpdateTabSpecificPermissions)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ClearTabSpecificPermissions,
+ OnClearTabSpecificPermissions)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UsingWebRequestAPI, OnUsingWebRequestAPI)
+ IPC_MESSAGE_FORWARD(ExtensionMsg_WatchPages,
+ content_watcher_.get(),
+ ContentWatcher::OnWatchPages)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ return handled;
+}
+void Dispatcher::IdleNotification() {
+ if (set_idle_notifications_ && forced_idle_timer_) {
+ // Dampen the forced delay as well if the extension stays idle for long
+ // periods of time. (forced_idle_timer_ can be NULL after
+ // OnRenderProcessShutdown has been called.)
+ int64_t forced_delay_ms =
+ std::max(RenderThread::Get()->GetIdleNotificationDelayInMs(),
+ kMaxExtensionIdleHandlerDelayMs);
+ forced_idle_timer_->Stop();
+ forced_idle_timer_->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(forced_delay_ms),
+ RenderThread::Get(),
+ &RenderThread::IdleHandler);
+ }
+}
+
+void Dispatcher::OnRenderProcessShutdown() {
+ v8_schema_registry_.reset();
+ forced_idle_timer_.reset();
+ content_watcher_.reset();
+ script_context_set_->ForEach(
+ std::string(), nullptr,
+ base::Bind(&ScriptContextSet::Remove,
+ base::Unretained(script_context_set_.get())));
+}
+
+void Dispatcher::OnActivateExtension(const std::string& extension_id) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (!extension) {
+ // Extension was activated but was never loaded. This probably means that
+ // the renderer failed to load it (or the browser failed to tell us when it
+ // did). Failures shouldn't happen, but instead of crashing there (which
+ // executes on all renderers) be conservative and only crash in the renderer
+ // of the extension which failed to load; this one.
+ std::string& error = extension_load_errors_[extension_id];
+ char minidump[256];
+ base::debug::Alias(&minidump);
+ base::snprintf(minidump,
+ arraysize(minidump),
+ "e::dispatcher:%s:%s",
+ extension_id.c_str(),
+ error.c_str());
+ LOG(FATAL) << extension_id << " was never loaded: " << error;
+ }
+
+ active_extension_ids_.insert(extension_id);
+
+ // This is called when starting a new extension page, so start the idle
+ // handler ticking.
+ RenderThread::Get()->ScheduleIdleHandler(kInitialExtensionIdleHandlerDelayMs);
+
+ DOMActivityLogger::AttachToWorld(
+ DOMActivityLogger::kMainWorldId, extension_id);
+
+ InitOriginPermissions(extension);
+
+ UpdateActiveExtensions();
+}
+
+void Dispatcher::OnCancelSuspend(const std::string& extension_id) {
+ DispatchEvent(extension_id, kOnSuspendCanceledEvent);
+}
+
+void Dispatcher::OnDeliverMessage(int target_port_id, const Message& message) {
+ scoped_ptr<RequestSender::ScopedTabID> scoped_tab_id;
+ std::map<int, int>::const_iterator it =
+ port_to_tab_id_map_.find(target_port_id);
+ if (it != port_to_tab_id_map_.end()) {
+ scoped_tab_id.reset(
+ new RequestSender::ScopedTabID(request_sender(), it->second));
+ }
+
+ MessagingBindings::DeliverMessage(*script_context_set_, target_port_id,
+ message,
+ NULL); // All render frames.
+}
+
+void Dispatcher::OnDispatchOnConnect(
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id) {
+ DCHECK(!ContainsKey(port_to_tab_id_map_, target_port_id));
+ DCHECK_EQ(1, target_port_id % 2); // target renderer ports have odd IDs.
+ int sender_tab_id = -1;
+ source.tab.GetInteger("id", &sender_tab_id);
+ port_to_tab_id_map_[target_port_id] = sender_tab_id;
+
+ MessagingBindings::DispatchOnConnect(*script_context_set_, target_port_id,
+ channel_name, source, info,
+ tls_channel_id,
+ NULL); // All render frames.
+}
+
+void Dispatcher::OnDispatchOnDisconnect(int port_id,
+ const std::string& error_message) {
+ MessagingBindings::DispatchOnDisconnect(*script_context_set_, port_id,
+ error_message,
+ NULL); // All render frames.
+}
+
+void Dispatcher::OnLoaded(
+ const std::vector<ExtensionMsg_Loaded_Params>& loaded_extensions) {
+ for (const auto& param : loaded_extensions) {
+ std::string error;
+ scoped_refptr<const Extension> extension = param.ConvertToExtension(&error);
+ if (!extension.get()) {
+ NOTREACHED() << error;
+ // Note: in tests |param.id| has been observed to be empty (see comment
+ // just below) so this isn't all that reliable.
+ extension_load_errors_[param.id] = error;
+ continue;
+ }
+
+ RendererExtensionRegistry* extension_registry =
+ RendererExtensionRegistry::Get();
+ // TODO(kalman): This test is deliberately not a CHECK (though I wish it
+ // could be) and uses extension->id() not params.id:
+ // 1. For some reason params.id can be empty. I've only seen it with
+ // the webstore extension, in tests, and I've spent some time trying to
+ // figure out why - but cost/benefit won.
+ // 2. The browser only sends this IPC to RenderProcessHosts once, but the
+ // Dispatcher is attached to a RenderThread. Presumably there is a
+ // mismatch there. In theory one would think it's possible for the
+ // browser to figure this out itself - but again, cost/benefit.
+ if (!extension_registry->Contains(extension->id()))
+ extension_registry->Insert(extension);
+ }
+
+ // Update the available bindings for all contexts. These may have changed if
+ // an externally_connectable extension was loaded that can connect to an
+ // open webpage.
+ UpdateBindings("");
+}
+
+void Dispatcher::OnMessageInvoke(const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture) {
+ InvokeModuleSystemMethod(
+ NULL, extension_id, module_name, function_name, args, user_gesture);
+}
+
+void Dispatcher::OnSetChannel(int channel) {
+ delegate_->SetChannel(channel);
+}
+
+void Dispatcher::OnSetScriptingWhitelist(
+ const ExtensionsClient::ScriptingWhitelist& extension_ids) {
+ ExtensionsClient::Get()->SetScriptingWhitelist(extension_ids);
+}
+
+void Dispatcher::OnSetSystemFont(const std::string& font_family,
+ const std::string& font_size) {
+ system_font_family_ = font_family;
+ system_font_size_ = font_size;
+}
+
+void Dispatcher::OnSetWebViewPartitionID(const std::string& partition_id) {
+ // |webview_partition_id_| cannot be changed once set.
+ CHECK(webview_partition_id_.empty() || webview_partition_id_ == partition_id);
+ webview_partition_id_ = partition_id;
+}
+
+void Dispatcher::OnShouldSuspend(const std::string& extension_id,
+ uint64_t sequence_id) {
+ RenderThread::Get()->Send(
+ new ExtensionHostMsg_ShouldSuspendAck(extension_id, sequence_id));
+}
+
+void Dispatcher::OnSuspend(const std::string& extension_id) {
+ // Dispatch the suspend event. This doesn't go through the standard event
+ // dispatch machinery because it requires special handling. We need to let
+ // the browser know when we are starting and stopping the event dispatch, so
+ // that it still considers the extension idle despite any activity the suspend
+ // event creates.
+ DispatchEvent(extension_id, kOnSuspendEvent);
+ RenderThread::Get()->Send(new ExtensionHostMsg_SuspendAck(extension_id));
+}
+
+void Dispatcher::OnTransferBlobs(const std::vector<std::string>& blob_uuids) {
+ RenderThread::Get()->Send(new ExtensionHostMsg_TransferBlobsAck(blob_uuids));
+}
+
+void Dispatcher::OnUnloaded(const std::string& id) {
+ // See comment in OnLoaded for why it would be nice, but perhaps incorrect,
+ // to CHECK here rather than guarding.
+ if (!RendererExtensionRegistry::Get()->Remove(id))
+ return;
+
+ active_extension_ids_.erase(id);
+
+ script_injection_manager_->OnExtensionUnloaded(id);
+
+ // If the extension is later reloaded with a different set of permissions,
+ // we'd like it to get a new isolated world ID, so that it can pick up the
+ // changed origin whitelist.
+ ScriptInjection::RemoveIsolatedWorld(id);
+
+ // Invalidate all of the contexts that were removed.
+ // TODO(kalman): add an invalidation observer interface to ScriptContext.
+ std::set<ScriptContext*> removed_contexts =
+ script_context_set_->OnExtensionUnloaded(id);
+ for (ScriptContext* context : removed_contexts) {
+ request_sender_->InvalidateSource(context);
+ }
+
+ // Update the available bindings for the remaining contexts. These may have
+ // changed if an externally_connectable extension is unloaded and a webpage
+ // is no longer accessible.
+ UpdateBindings("");
+
+ // Invalidates the messages map for the extension in case the extension is
+ // reloaded with a new messages map.
+ EraseL10nMessagesMap(id);
+
+ // We don't do anything with existing platform-app stylesheets. They will
+ // stay resident, but the URL pattern corresponding to the unloaded
+ // extension's URL just won't match anything anymore.
+}
+
+void Dispatcher::OnUpdatePermissions(
+ const ExtensionMsg_UpdatePermissions_Params& params) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(params.extension_id);
+ if (!extension)
+ return;
+
+ scoped_ptr<const PermissionSet> active =
+ params.active_permissions.ToPermissionSet();
+ scoped_ptr<const PermissionSet> withheld =
+ params.withheld_permissions.ToPermissionSet();
+
+ UpdateOriginPermissions(
+ extension->url(),
+ extension->permissions_data()->GetEffectiveHostPermissions(),
+ active->effective_hosts());
+
+ extension->permissions_data()->SetPermissions(std::move(active),
+ std::move(withheld));
+ UpdateBindings(extension->id());
+}
+
+void Dispatcher::OnUpdateTabSpecificPermissions(const GURL& visible_url,
+ const std::string& extension_id,
+ const URLPatternSet& new_hosts,
+ bool update_origin_whitelist,
+ int tab_id) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (!extension)
+ return;
+
+ URLPatternSet old_effective =
+ extension->permissions_data()->GetEffectiveHostPermissions();
+ extension->permissions_data()->UpdateTabSpecificPermissions(
+ tab_id,
+ extensions::PermissionSet(extensions::APIPermissionSet(),
+ extensions::ManifestPermissionSet(), new_hosts,
+ extensions::URLPatternSet()));
+
+ if (update_origin_whitelist) {
+ UpdateOriginPermissions(
+ extension->url(),
+ old_effective,
+ extension->permissions_data()->GetEffectiveHostPermissions());
+ }
+}
+
+void Dispatcher::OnClearTabSpecificPermissions(
+ const std::vector<std::string>& extension_ids,
+ bool update_origin_whitelist,
+ int tab_id) {
+ for (const std::string& id : extension_ids) {
+ const Extension* extension = RendererExtensionRegistry::Get()->GetByID(id);
+ if (extension) {
+ URLPatternSet old_effective =
+ extension->permissions_data()->GetEffectiveHostPermissions();
+ extension->permissions_data()->ClearTabSpecificPermissions(tab_id);
+ if (update_origin_whitelist) {
+ UpdateOriginPermissions(
+ extension->url(),
+ old_effective,
+ extension->permissions_data()->GetEffectiveHostPermissions());
+ }
+ }
+ }
+}
+
+void Dispatcher::OnUsingWebRequestAPI(bool webrequest_used) {
+ webrequest_used_ = webrequest_used;
+}
+
+void Dispatcher::OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) {
+ UpdateActiveExtensions();
+}
+
+void Dispatcher::UpdateActiveExtensions() {
+ std::set<std::string> active_extensions = active_extension_ids_;
+ user_script_set_manager_->GetAllActiveExtensionIds(&active_extensions);
+ delegate_->OnActiveExtensionsUpdated(active_extensions);
+}
+
+void Dispatcher::InitOriginPermissions(const Extension* extension) {
+ delegate_->InitOriginPermissions(extension,
+ IsExtensionActive(extension->id()));
+ UpdateOriginPermissions(
+ extension->url(),
+ URLPatternSet(), // No old permissions.
+ extension->permissions_data()->GetEffectiveHostPermissions());
+}
+
+void Dispatcher::UpdateOriginPermissions(const GURL& extension_url,
+ const URLPatternSet& old_patterns,
+ const URLPatternSet& new_patterns) {
+ static const char* kSchemes[] = {
+ url::kHttpScheme,
+ url::kHttpsScheme,
+ url::kFileScheme,
+ content::kChromeUIScheme,
+ url::kFtpScheme,
+#if defined(OS_CHROMEOS)
+ content::kExternalFileScheme,
+#endif
+ extensions::kExtensionScheme,
+ };
+ for (size_t i = 0; i < arraysize(kSchemes); ++i) {
+ const char* scheme = kSchemes[i];
+ // Remove all old patterns...
+ for (URLPatternSet::const_iterator pattern = old_patterns.begin();
+ pattern != old_patterns.end(); ++pattern) {
+ if (pattern->MatchesScheme(scheme)) {
+ WebSecurityPolicy::removeOriginAccessWhitelistEntry(
+ extension_url,
+ WebString::fromUTF8(scheme),
+ WebString::fromUTF8(pattern->host()),
+ pattern->match_subdomains());
+ }
+ }
+ // ...And add the new ones.
+ for (URLPatternSet::const_iterator pattern = new_patterns.begin();
+ pattern != new_patterns.end(); ++pattern) {
+ if (pattern->MatchesScheme(scheme)) {
+ WebSecurityPolicy::addOriginAccessWhitelistEntry(
+ extension_url,
+ WebString::fromUTF8(scheme),
+ WebString::fromUTF8(pattern->host()),
+ pattern->match_subdomains());
+ }
+ }
+ }
+}
+
+void Dispatcher::EnableCustomElementWhiteList() {
+ blink::WebCustomElement::addEmbedderCustomElementName("appview");
+ blink::WebCustomElement::addEmbedderCustomElementName("appviewbrowserplugin");
+ blink::WebCustomElement::addEmbedderCustomElementName("extensionoptions");
+ blink::WebCustomElement::addEmbedderCustomElementName(
+ "extensionoptionsbrowserplugin");
+ blink::WebCustomElement::addEmbedderCustomElementName("extensionview");
+ blink::WebCustomElement::addEmbedderCustomElementName(
+ "extensionviewbrowserplugin");
+ blink::WebCustomElement::addEmbedderCustomElementName("webview");
+ blink::WebCustomElement::addEmbedderCustomElementName("webviewbrowserplugin");
+}
+
+void Dispatcher::UpdateBindings(const std::string& extension_id) {
+ script_context_set().ForEach(extension_id,
+ base::Bind(&Dispatcher::UpdateBindingsForContext,
+ base::Unretained(this)));
+}
+
+void Dispatcher::UpdateBindingsForContext(ScriptContext* context) {
+ v8::HandleScope handle_scope(context->isolate());
+ v8::Context::Scope context_scope(context->v8_context());
+
+ // TODO(kalman): Make the bindings registration have zero overhead then run
+ // the same code regardless of context type.
+ switch (context->context_type()) {
+ case Feature::UNSPECIFIED_CONTEXT:
+ case Feature::WEB_PAGE_CONTEXT:
+ case Feature::BLESSED_WEB_PAGE_CONTEXT:
+ // Hard-code registration of any APIs that are exposed to webpage-like
+ // contexts, because it's too expensive to run the full bindings code.
+ // All of the same permission checks will still apply.
+ if (context->GetAvailability("app").is_available())
+ RegisterBinding("app", context);
+ if (context->GetAvailability("webstore").is_available())
+ RegisterBinding("webstore", context);
+ if (context->GetAvailability("dashboardPrivate").is_available())
+ RegisterBinding("dashboardPrivate", context);
+ if (IsRuntimeAvailableToContext(context))
+ RegisterBinding("runtime", context);
+ UpdateContentCapabilities(context);
+ break;
+
+ case Feature::BLESSED_EXTENSION_CONTEXT:
+ case Feature::UNBLESSED_EXTENSION_CONTEXT:
+ case Feature::CONTENT_SCRIPT_CONTEXT:
+ case Feature::WEBUI_CONTEXT: {
+ // Extension context; iterate through all the APIs and bind the available
+ // ones.
+ const FeatureProvider* api_feature_provider =
+ FeatureProvider::GetAPIFeatures();
+ for (const auto& map_entry : api_feature_provider->GetAllFeatures()) {
+ // Internal APIs are included via require(api_name) from internal code
+ // rather than chrome[api_name].
+ if (map_entry.second->IsInternal())
+ continue;
+
+ // If this API has a parent feature (and isn't marked 'noparent'),
+ // then this must be a function or event, so we should not register.
+ if (api_feature_provider->GetParent(map_entry.second.get()) != nullptr)
+ continue;
+
+ // Skip chrome.test if this isn't a test.
+ if (map_entry.first == "test" &&
+ !base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kTestType)) {
+ continue;
+ }
+
+ if (context->IsAnyFeatureAvailableToContext(*map_entry.second.get()))
+ RegisterBinding(map_entry.first, context);
+ }
+ break;
+ }
+ case Feature::SERVICE_WORKER_CONTEXT:
+ // Handled in DidInitializeServiceWorkerContextOnWorkerThread().
+ NOTREACHED();
+ break;
+ }
+}
+
+void Dispatcher::RegisterBinding(const std::string& api_name,
+ ScriptContext* context) {
+ std::string bind_name;
+ v8::Local<v8::Object> bind_object =
+ GetOrCreateBindObjectIfAvailable(api_name, &bind_name, context);
+
+ // Empty if the bind object failed to be created, probably because the
+ // extension overrode chrome with a non-object, e.g. window.chrome = true.
+ if (bind_object.IsEmpty())
+ return;
+
+ v8::Local<v8::String> v8_bind_name =
+ v8::String::NewFromUtf8(context->isolate(), bind_name.c_str());
+ if (bind_object->HasRealNamedProperty(v8_bind_name)) {
+ // The bind object may already have the property if the API has been
+ // registered before (or if the extension has put something there already,
+ // but, whatevs).
+ //
+ // In the former case, we need to re-register the bindings for the APIs
+ // which the extension now has permissions for (if any), but not touch any
+ // others so that we don't destroy state such as event listeners.
+ //
+ // TODO(kalman): Only register available APIs to make this all moot.
+ if (bind_object->HasRealNamedCallbackProperty(v8_bind_name))
+ return; // lazy binding still there, nothing to do
+ if (bind_object->Get(v8_bind_name)->IsObject())
+ return; // binding has already been fully installed
+ }
+
+ ModuleSystem* module_system = context->module_system();
+ if (!source_map_.Contains(api_name)) {
+ module_system->RegisterNativeHandler(
+ api_name,
+ scoped_ptr<NativeHandler>(new BindingGeneratingNativeHandler(
+ context, api_name, "binding")));
+ module_system->SetNativeLazyField(
+ bind_object, bind_name, api_name, "binding");
+ } else {
+ module_system->SetLazyField(bind_object, bind_name, api_name, "binding");
+ }
+}
+
+// NOTE: please use the naming convention "foo_natives" for these.
+void Dispatcher::RegisterNativeHandlers(ModuleSystem* module_system,
+ ScriptContext* context) {
+ RegisterNativeHandlers(module_system,
+ context,
+ this,
+ request_sender_.get(),
+ v8_schema_registry_.get());
+ const Extension* extension = context->extension();
+ int manifest_version = extension ? extension->manifest_version() : 1;
+ bool is_component_extension =
+ extension && Manifest::IsComponentLocation(extension->location());
+ bool send_request_disabled =
+ (extension && Manifest::IsUnpackedLocation(extension->location()) &&
+ BackgroundInfo::HasLazyBackgroundPage(extension));
+ module_system->RegisterNativeHandler(
+ "process",
+ scoped_ptr<NativeHandler>(new ProcessInfoNativeHandler(
+ context,
+ context->GetExtensionID(),
+ context->GetContextTypeDescription(),
+ ExtensionsRendererClient::Get()->IsIncognitoProcess(),
+ is_component_extension,
+ manifest_version,
+ send_request_disabled)));
+
+ delegate_->RegisterNativeHandlers(this, module_system, context);
+}
+
+bool Dispatcher::IsRuntimeAvailableToContext(ScriptContext* context) {
+ for (const auto& extension :
+ *RendererExtensionRegistry::Get()->GetMainThreadExtensionSet()) {
+ ExternallyConnectableInfo* info = static_cast<ExternallyConnectableInfo*>(
+ extension->GetManifestData(manifest_keys::kExternallyConnectable));
+ if (info && info->matches.MatchesURL(context->url()))
+ return true;
+ }
+ return false;
+}
+
+void Dispatcher::UpdateContentCapabilities(ScriptContext* context) {
+ APIPermissionSet permissions;
+ for (const auto& extension :
+ *RendererExtensionRegistry::Get()->GetMainThreadExtensionSet()) {
+ const ContentCapabilitiesInfo& info =
+ ContentCapabilitiesInfo::Get(extension.get());
+ if (info.url_patterns.MatchesURL(context->url())) {
+ APIPermissionSet new_permissions;
+ APIPermissionSet::Union(permissions, info.permissions, &new_permissions);
+ permissions = new_permissions;
+ }
+ }
+ context->set_content_capabilities(permissions);
+}
+
+void Dispatcher::PopulateSourceMap() {
+ const std::vector<std::pair<std::string, int> > resources = GetJsResources();
+ for (std::vector<std::pair<std::string, int> >::const_iterator resource =
+ resources.begin();
+ resource != resources.end();
+ ++resource) {
+ source_map_.RegisterSource(resource->first, resource->second);
+ }
+ delegate_->PopulateSourceMap(&source_map_);
+}
+
+bool Dispatcher::IsWithinPlatformApp() {
+ for (std::set<std::string>::iterator iter = active_extension_ids_.begin();
+ iter != active_extension_ids_.end();
+ ++iter) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(*iter);
+ if (extension && extension->is_platform_app())
+ return true;
+ }
+ return false;
+}
+
+v8::Local<v8::Object> Dispatcher::GetOrCreateObject(
+ const v8::Local<v8::Object>& object,
+ const std::string& field,
+ v8::Isolate* isolate) {
+ v8::Local<v8::String> key = v8::String::NewFromUtf8(isolate, field.c_str());
+ // If the object has a callback property, it is assumed it is an unavailable
+ // API, so it is safe to delete. This is checked before GetOrCreateObject is
+ // called.
+ if (object->HasRealNamedCallbackProperty(key)) {
+ object->Delete(key);
+ } else if (object->HasRealNamedProperty(key)) {
+ v8::Local<v8::Value> value = object->Get(key);
+ CHECK(value->IsObject());
+ return v8::Local<v8::Object>::Cast(value);
+ }
+
+ v8::Local<v8::Object> new_object = v8::Object::New(isolate);
+ object->Set(key, new_object);
+ return new_object;
+}
+
+v8::Local<v8::Object> Dispatcher::GetOrCreateBindObjectIfAvailable(
+ const std::string& api_name,
+ std::string* bind_name,
+ ScriptContext* context) {
+ std::vector<std::string> split = base::SplitString(
+ api_name, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ v8::Local<v8::Object> bind_object;
+
+ // Check if this API has an ancestor. If the API's ancestor is available and
+ // the API is not available, don't install the bindings for this API. If
+ // the API is available and its ancestor is not, delete the ancestor and
+ // install the bindings for the API. This is to prevent loading the ancestor
+ // API schema if it will not be needed.
+ //
+ // For example:
+ // If app is available and app.window is not, just install app.
+ // If app.window is available and app is not, delete app and install
+ // app.window on a new object so app does not have to be loaded.
+ const FeatureProvider* api_feature_provider =
+ FeatureProvider::GetAPIFeatures();
+ std::string ancestor_name;
+ bool only_ancestor_available = false;
+
+ for (size_t i = 0; i < split.size() - 1; ++i) {
+ ancestor_name += (i ? "." : "") + split[i];
+ if (api_feature_provider->GetFeature(ancestor_name) &&
+ context->GetAvailability(ancestor_name).is_available() &&
+ !context->GetAvailability(api_name).is_available()) {
+ only_ancestor_available = true;
+ break;
+ }
+
+ if (bind_object.IsEmpty()) {
+ bind_object = AsObjectOrEmpty(GetOrCreateChrome(context));
+ if (bind_object.IsEmpty())
+ return v8::Local<v8::Object>();
+ }
+ bind_object = GetOrCreateObject(bind_object, split[i], context->isolate());
+ }
+
+ if (only_ancestor_available)
+ return v8::Local<v8::Object>();
+
+ if (bind_name)
+ *bind_name = split.back();
+
+ return bind_object.IsEmpty() ? AsObjectOrEmpty(GetOrCreateChrome(context))
+ : bind_object;
+}
+
+void Dispatcher::RequireGuestViewModules(ScriptContext* context) {
+ Feature::Context context_type = context->context_type();
+ ModuleSystem* module_system = context->module_system();
+
+ // Only set if |context| is capable of running guests in OOPIF. Used to
+ // require additional module overrides.
+ bool guest_view_required = false;
+
+ // Require AppView.
+ if (context->GetAvailability("appViewEmbedderInternal").is_available()) {
+ module_system->Require("appView");
+ }
+
+ // Require ExtensionOptions.
+ if (context->GetAvailability("extensionOptionsInternal").is_available()) {
+ module_system->Require("extensionOptions");
+ module_system->Require("extensionOptionsAttributes");
+
+ guest_view_required = true;
+ }
+
+ // Require ExtensionView.
+ if (context->GetAvailability("extensionViewInternal").is_available()) {
+ module_system->Require("extensionView");
+ module_system->Require("extensionViewApiMethods");
+ module_system->Require("extensionViewAttributes");
+ }
+
+ // Require WebView.
+ if (context->GetAvailability("webViewInternal").is_available()) {
+ module_system->Require("webView");
+ module_system->Require("webViewApiMethods");
+ module_system->Require("webViewAttributes");
+ if (context->GetAvailability("webViewExperimentalInternal")
+ .is_available()) {
+ module_system->Require("webViewExperimental");
+ }
+
+ guest_view_required = true;
+ }
+
+ if (guest_view_required &&
+ content::BrowserPluginGuestMode::UseCrossProcessFramesForGuests()) {
+ module_system->Require("guestViewIframe");
+ module_system->Require("guestViewIframeContainer");
+ }
+
+ // The "guestViewDeny" module must always be loaded last. It registers
+ // error-providing custom elements for the GuestView types that are not
+ // available, and thus all of those types must have been checked and loaded
+ // (or not loaded) beforehand.
+ if (context_type == Feature::BLESSED_EXTENSION_CONTEXT) {
+ module_system->Require("guestViewDeny");
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/dispatcher.h b/chromium/extensions/renderer/dispatcher.h
new file mode 100644
index 00000000000..37ab0a312dc
--- /dev/null
+++ b/chromium/extensions/renderer/dispatcher.h
@@ -0,0 +1,318 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_DISPATCHER_H_
+#define EXTENSIONS_RENDERER_DISPATCHER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/timer/timer.h"
+#include "content/public/renderer/render_process_observer.h"
+#include "extensions/common/event_filter.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/resource_bundle_source_map.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/user_script_set_manager.h"
+#include "extensions/renderer/v8_schema_registry.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebVector.h"
+#include "v8/include/v8.h"
+
+class ChromeRenderViewTest;
+class GURL;
+class ModuleSystem;
+class URLPattern;
+struct ExtensionMsg_ExternalConnectionInfo;
+struct ExtensionMsg_Loaded_Params;
+struct ExtensionMsg_TabConnectionInfo;
+struct ExtensionMsg_UpdatePermissions_Params;
+
+namespace blink {
+class WebFrame;
+class WebLocalFrame;
+class WebSecurityOrigin;
+}
+
+namespace base {
+class ListValue;
+}
+
+namespace content {
+class RenderThread;
+}
+
+namespace extensions {
+class ContentWatcher;
+class DispatcherDelegate;
+class FilteredEventRouter;
+class ManifestPermissionSet;
+class RequestSender;
+class ScriptContext;
+class ScriptInjectionManager;
+struct Message;
+
+// Dispatches extension control messages sent to the renderer and stores
+// renderer extension related state.
+class Dispatcher : public content::RenderProcessObserver,
+ public UserScriptSetManager::Observer {
+ public:
+ explicit Dispatcher(DispatcherDelegate* delegate);
+ ~Dispatcher() override;
+
+ const ScriptContextSet& script_context_set() const {
+ return *script_context_set_;
+ }
+
+ V8SchemaRegistry* v8_schema_registry() { return v8_schema_registry_.get(); }
+
+ ContentWatcher* content_watcher() { return content_watcher_.get(); }
+
+ RequestSender* request_sender() { return request_sender_.get(); }
+
+ const std::string& webview_partition_id() { return webview_partition_id_; }
+
+ void OnRenderFrameCreated(content::RenderFrame* render_frame);
+
+ bool IsExtensionActive(const std::string& extension_id) const;
+
+ void DidCreateScriptContext(blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& context,
+ int extension_group,
+ int world_id);
+
+ // Runs on a different thread and should not use any member variables.
+ static void DidInitializeServiceWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> v8_context,
+ const GURL& url);
+
+ void WillReleaseScriptContext(blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& context,
+ int world_id);
+
+ // Runs on a different thread and should not use any member variables.
+ static void WillDestroyServiceWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> v8_context,
+ const GURL& url);
+
+ // This method is not allowed to run JavaScript code in the frame.
+ void DidCreateDocumentElement(blink::WebLocalFrame* frame);
+
+ // These methods may run (untrusted) JavaScript code in the frame, and
+ // cause |render_frame| to become invalid.
+ void RunScriptsAtDocumentStart(content::RenderFrame* render_frame);
+ void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame);
+
+ void OnExtensionResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error);
+
+ // Dispatches the event named |event_name| to all render views.
+ void DispatchEvent(const std::string& extension_id,
+ const std::string& event_name) const;
+
+ // Shared implementation of the various MessageInvoke IPCs.
+ void InvokeModuleSystemMethod(content::RenderFrame* render_frame,
+ const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture);
+
+ void ClearPortData(int port_id);
+
+ // Returns a list of (module name, resource id) pairs for the JS modules to
+ // add to the source map.
+ static std::vector<std::pair<std::string, int> > GetJsResources();
+ static void RegisterNativeHandlers(ModuleSystem* module_system,
+ ScriptContext* context,
+ Dispatcher* dispatcher,
+ RequestSender* request_sender,
+ V8SchemaRegistry* v8_schema_registry);
+
+ bool WasWebRequestUsedBySomeExtensions() const { return webrequest_used_; }
+
+ private:
+ // The RendererPermissionsPolicyDelegateTest.CannotScriptWebstore test needs
+ // to call the OnActivateExtension IPCs.
+ friend class ::ChromeRenderViewTest;
+ FRIEND_TEST_ALL_PREFIXES(RendererPermissionsPolicyDelegateTest,
+ CannotScriptWebstore);
+
+ // RenderProcessObserver implementation:
+ bool OnControlMessageReceived(const IPC::Message& message) override;
+ void IdleNotification() override;
+ void OnRenderProcessShutdown() override;
+
+ void OnActivateExtension(const std::string& extension_id);
+ void OnCancelSuspend(const std::string& extension_id);
+ void OnDeliverMessage(int target_port_id, const Message& message);
+ void OnDispatchOnConnect(int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id);
+ void OnDispatchOnDisconnect(int port_id, const std::string& error_message);
+ void OnLoaded(
+ const std::vector<ExtensionMsg_Loaded_Params>& loaded_extensions);
+ void OnMessageInvoke(const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture);
+ void OnSetChannel(int channel);
+ void OnSetScriptingWhitelist(
+ const ExtensionsClient::ScriptingWhitelist& extension_ids);
+ void OnSetSystemFont(const std::string& font_family,
+ const std::string& font_size);
+ void OnSetWebViewPartitionID(const std::string& partition_id);
+ void OnShouldSuspend(const std::string& extension_id, uint64_t sequence_id);
+ void OnSuspend(const std::string& extension_id);
+ void OnTransferBlobs(const std::vector<std::string>& blob_uuids);
+ void OnUnloaded(const std::string& id);
+ void OnUpdatePermissions(const ExtensionMsg_UpdatePermissions_Params& params);
+ void OnUpdateTabSpecificPermissions(const GURL& visible_url,
+ const std::string& extension_id,
+ const URLPatternSet& new_hosts,
+ bool update_origin_whitelist,
+ int tab_id);
+ void OnClearTabSpecificPermissions(
+ const std::vector<std::string>& extension_ids,
+ bool update_origin_whitelist,
+ int tab_id);
+ void OnUsingWebRequestAPI(bool webrequest_used);
+
+ // UserScriptSetManager::Observer implementation.
+ void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) override;
+
+ void UpdateActiveExtensions();
+
+ // Sets up the host permissions for |extension|.
+ void InitOriginPermissions(const Extension* extension);
+
+ // Updates the host permissions for the extension url to include only those in
+ // |new_patterns|, and remove from |old_patterns| that are no longer allowed.
+ void UpdateOriginPermissions(const GURL& extension_url,
+ const URLPatternSet& old_patterns,
+ const URLPatternSet& new_patterns);
+
+ // Enable custom element whitelist in Apps.
+ void EnableCustomElementWhiteList();
+
+ // Adds or removes bindings for every context belonging to |extension_id|, or
+ // or all contexts if |extension_id| is empty.
+ void UpdateBindings(const std::string& extension_id);
+
+ void UpdateBindingsForContext(ScriptContext* context);
+
+ void RegisterBinding(const std::string& api_name, ScriptContext* context);
+
+ void RegisterNativeHandlers(ModuleSystem* module_system,
+ ScriptContext* context);
+
+ // Determines if a ScriptContext can connect to any externally_connectable-
+ // enabled extension.
+ bool IsRuntimeAvailableToContext(ScriptContext* context);
+
+ // Updates a web page context with any content capabilities granted by active
+ // extensions.
+ void UpdateContentCapabilities(ScriptContext* context);
+
+ // Inserts static source code into |source_map_|.
+ void PopulateSourceMap();
+
+ // Returns whether the current renderer hosts a platform app.
+ bool IsWithinPlatformApp();
+
+ // Gets |field| from |object| or creates it as an empty object if it doesn't
+ // exist.
+ v8::Local<v8::Object> GetOrCreateObject(const v8::Local<v8::Object>& object,
+ const std::string& field,
+ v8::Isolate* isolate);
+
+ v8::Local<v8::Object> GetOrCreateBindObjectIfAvailable(
+ const std::string& api_name,
+ std::string* bind_name,
+ ScriptContext* context);
+
+ // Requires the GuestView modules in the module system of the ScriptContext
+ // |context|.
+ void RequireGuestViewModules(ScriptContext* context);
+
+ // The delegate for this dispatcher. Not owned, but must extend beyond the
+ // Dispatcher's own lifetime.
+ DispatcherDelegate* delegate_;
+
+ // True if the IdleNotification timer should be set.
+ bool set_idle_notifications_;
+
+ // The IDs of extensions that failed to load, mapped to the error message
+ // generated on failure.
+ std::map<std::string, std::string> extension_load_errors_;
+
+ // All the bindings contexts that are currently loaded for this renderer.
+ // There is zero or one for each v8 context.
+ scoped_ptr<ScriptContextSet> script_context_set_;
+
+ scoped_ptr<ContentWatcher> content_watcher_;
+
+ scoped_ptr<UserScriptSetManager> user_script_set_manager_;
+
+ scoped_ptr<ScriptInjectionManager> script_injection_manager_;
+
+ // Same as above, but on a longer timer and will run even if the process is
+ // not idle, to ensure that IdleHandle gets called eventually.
+ scoped_ptr<base::RepeatingTimer> forced_idle_timer_;
+
+ // The extensions and apps that are active in this process.
+ ExtensionIdSet active_extension_ids_;
+
+ ResourceBundleSourceMap source_map_;
+
+ // Cache for the v8 representation of extension API schemas.
+ scoped_ptr<V8SchemaRegistry> v8_schema_registry_;
+
+ // Sends API requests to the extension host.
+ scoped_ptr<RequestSender> request_sender_;
+
+ // The platforms system font family and size;
+ std::string system_font_family_;
+ std::string system_font_size_;
+
+ // Mapping of port IDs to tabs. If there is no tab, the value would be -1.
+ std::map<int, int> port_to_tab_id_map_;
+
+ // It is important for this to come after the ScriptInjectionManager, so that
+ // the observer is destroyed before the UserScriptSet.
+ ScopedObserver<UserScriptSetManager, UserScriptSetManager::Observer>
+ user_script_set_manager_observer_;
+
+ // Status of webrequest usage.
+ bool webrequest_used_;
+
+ // The WebView partition ID associated with this process's storage partition,
+ // if this renderer is a WebView guest render process. Otherwise, this will be
+ // empty.
+ std::string webview_partition_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(Dispatcher);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_DISPATCHER_H_
diff --git a/chromium/extensions/renderer/dispatcher_delegate.h b/chromium/extensions/renderer/dispatcher_delegate.h
new file mode 100644
index 00000000000..f00c357b7ef
--- /dev/null
+++ b/chromium/extensions/renderer/dispatcher_delegate.h
@@ -0,0 +1,59 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_DISPATCHER_DELEGATE_H_
+#define EXTENSIONS_RENDERER_DISPATCHER_DELEGATE_H_
+
+#include <set>
+#include <string>
+
+namespace blink {
+class WebFrame;
+}
+
+namespace extensions {
+class Dispatcher;
+class Extension;
+class ModuleSystem;
+class ResourceBundleSourceMap;
+class ScriptContext;
+class URLPatternSet;
+
+// Base class and default implementation for an extensions::Dispacher delegate.
+// DispatcherDelegate can be used to override and extend the behavior of the
+// extensions system's renderer side.
+class DispatcherDelegate {
+ public:
+ virtual ~DispatcherDelegate() {}
+
+ // Initializes origin permissions for a newly created extension context.
+ virtual void InitOriginPermissions(const Extension* extension,
+ bool is_extension_active) {}
+
+ // Includes additional native handlers in a ScriptContext's ModuleSystem.
+ virtual void RegisterNativeHandlers(Dispatcher* dispatcher,
+ ModuleSystem* module_system,
+ ScriptContext* context) {}
+
+ // Includes additional source resources into the resource map.
+ virtual void PopulateSourceMap(ResourceBundleSourceMap* source_map) {}
+
+ // Requires additional modules within an extension context's module system.
+ virtual void RequireAdditionalModules(ScriptContext* context,
+ bool is_within_platform_app) {}
+
+ // Allows the delegate to respond to an updated set of active extensions in
+ // the Dispatcher.
+ virtual void OnActiveExtensionsUpdated(
+ const std::set<std::string>& extension_ids) {}
+
+ // Sets the current Chrome channel.
+ // TODO(rockot): This doesn't belong in a generic extensions system interface.
+ // See http://crbug.com/368431.
+ virtual void SetChannel(int channel) {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_DISPATCHER_DELEGATE_H_
diff --git a/chromium/extensions/renderer/display_source_custom_bindings.cc b/chromium/extensions/renderer/display_source_custom_bindings.cc
new file mode 100644
index 00000000000..1a021c157b3
--- /dev/null
+++ b/chromium/extensions/renderer/display_source_custom_bindings.cc
@@ -0,0 +1,306 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/display_source_custom_bindings.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "content/public/child/v8_value_converter.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/platform/WebMediaStream.h"
+#include "third_party/WebKit/public/platform/WebMediaStreamTrack.h"
+#include "third_party/WebKit/public/web/WebDOMMediaStreamTrack.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+using content::V8ValueConverter;
+
+namespace {
+const char kErrorNotSupported[] = "Not supported";
+const char kInvalidStreamArgs[] = "Invalid stream arguments";
+const char kSessionAlreadyStarted[] = "The session has been already started";
+const char kSessionAlreadyTerminating[] = "The session is already terminating";
+const char kSessionNotFound[] = "Session not found";
+} // namespace
+
+DisplaySourceCustomBindings::DisplaySourceCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context),
+ weak_factory_(this) {
+ RouteFunction("StartSession",
+ base::Bind(&DisplaySourceCustomBindings::StartSession,
+ weak_factory_.GetWeakPtr()));
+ RouteFunction("TerminateSession",
+ base::Bind(&DisplaySourceCustomBindings::TerminateSession,
+ weak_factory_.GetWeakPtr()));
+}
+
+DisplaySourceCustomBindings::~DisplaySourceCustomBindings() {
+}
+
+void DisplaySourceCustomBindings::Invalidate() {
+ session_map_.clear();
+ weak_factory_.InvalidateWeakPtrs();
+ ObjectBackedNativeHandler::Invalidate();
+}
+
+namespace {
+
+v8::Local<v8::Value> GetChildValue(v8::Local<v8::Object> value,
+ const std::string& key_name,
+ v8::Isolate* isolate) {
+ v8::Local<v8::Array> property_names(value->GetOwnPropertyNames());
+ for (uint32_t i = 0; i < property_names->Length(); ++i) {
+ v8::Local<v8::Value> key(property_names->Get(i));
+ if (key_name == *v8::String::Utf8Value(key)) {
+ v8::TryCatch try_catch(isolate);
+ v8::Local<v8::Value> child_v8 = value->Get(key);
+ if (try_catch.HasCaught()) {
+ return v8::Null(isolate);
+ }
+ return child_v8;
+ }
+ }
+
+ return v8::Null(isolate);
+}
+
+int32_t GetCallbackId() {
+ static int32_t sCallId = 0;
+ return ++sCallId;
+}
+
+} // namespace
+
+void DisplaySourceCustomBindings::StartSession(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsObject());
+
+ v8::Isolate* isolate = context()->isolate();
+ v8::Local<v8::Object> start_info = args[0].As<v8::Object>();
+
+ v8::Local<v8::Value> sink_id_val =
+ GetChildValue(start_info, "sinkId", isolate);
+ CHECK(sink_id_val->IsInt32());
+ const int sink_id = sink_id_val->ToInt32(isolate)->Value();
+ if (GetDisplaySession(sink_id)) {
+ isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(
+ isolate, kSessionAlreadyStarted)));
+ return;
+ }
+
+ v8::Local<v8::Value> video_stream_val =
+ GetChildValue(start_info, "videoTrack", isolate);
+ v8::Local<v8::Value> audio_stream_val =
+ GetChildValue(start_info, "audioTrack", isolate);
+
+ if ((video_stream_val->IsNull() || video_stream_val->IsUndefined()) &&
+ (audio_stream_val->IsNull() || audio_stream_val->IsUndefined())) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidStreamArgs)));
+ return;
+ }
+
+ blink::WebMediaStreamTrack audio_track, video_track;
+
+ if (!video_stream_val->IsNull() && !video_stream_val->IsUndefined()) {
+ CHECK(video_stream_val->IsObject());
+ video_track =
+ blink::WebDOMMediaStreamTrack::fromV8Value(
+ video_stream_val).component();
+ if (video_track.isNull()) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidStreamArgs)));
+ return;
+ }
+ }
+ if (!audio_stream_val->IsNull() && !audio_stream_val->IsUndefined()) {
+ CHECK(audio_stream_val->IsObject());
+ audio_track =
+ blink::WebDOMMediaStreamTrack::fromV8Value(
+ audio_stream_val).component();
+ if (audio_track.isNull()) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidStreamArgs)));
+ return;
+ }
+ }
+
+ scoped_ptr<DisplaySourceAuthInfo> auth_info;
+ v8::Local<v8::Value> auth_info_v8_val =
+ GetChildValue(start_info, "authenticationInfo", isolate);
+ if (!auth_info_v8_val->IsNull()) {
+ CHECK(auth_info_v8_val->IsObject());
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ scoped_ptr<base::Value> auth_info_val(
+ converter->FromV8Value(auth_info_v8_val, context()->v8_context()));
+ CHECK(auth_info_val);
+ auth_info = DisplaySourceAuthInfo::FromValue(*auth_info_val);
+ }
+
+ DisplaySourceSessionParams session_params;
+ session_params.sink_id = sink_id;
+ session_params.video_track = video_track;
+ session_params.audio_track = audio_track;
+ session_params.render_frame = context()->GetRenderFrame();
+ if (auth_info) {
+ session_params.auth_method = auth_info->method;
+ session_params.auth_data = auth_info->data ? *auth_info->data : "";
+ }
+ scoped_ptr<DisplaySourceSession> session =
+ DisplaySourceSessionFactory::CreateSession(session_params);
+ if (!session) {
+ isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(
+ isolate, kErrorNotSupported)));
+ return;
+ }
+
+ auto on_terminated_callback =
+ base::Bind(&DisplaySourceCustomBindings::OnSessionTerminated,
+ weak_factory_.GetWeakPtr(), sink_id);
+ auto on_error_callback =
+ base::Bind(&DisplaySourceCustomBindings::OnSessionError,
+ weak_factory_.GetWeakPtr(), sink_id);
+ session->SetNotificationCallbacks(on_terminated_callback, on_error_callback);
+
+ int32_t call_id = GetCallbackId();
+ args.GetReturnValue().Set(call_id);
+
+ auto on_call_completed =
+ base::Bind(&DisplaySourceCustomBindings::OnSessionStarted,
+ weak_factory_.GetWeakPtr(), sink_id, call_id);
+ session->Start(on_call_completed);
+ session_map_.insert(std::make_pair(sink_id, std::move(session)));
+}
+
+void DisplaySourceCustomBindings::TerminateSession(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsInt32());
+
+ v8::Isolate* isolate = context()->isolate();
+ int sink_id = args[0]->ToInt32(args.GetIsolate())->Value();
+ DisplaySourceSession* session = GetDisplaySession(sink_id);
+ if (!session) {
+ isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(
+ isolate, kSessionNotFound)));
+ return;
+ }
+
+ DisplaySourceSession::State state = session->state();
+ DCHECK_NE(state, DisplaySourceSession::Idle);
+ if (state == DisplaySourceSession::Establishing) {
+ // 'session started' callback has not yet been invoked.
+ // This session is not existing for the user.
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kSessionNotFound)));
+ return;
+ }
+
+ if (state == DisplaySourceSession::Terminating) {
+ isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(
+ isolate, kSessionAlreadyTerminating)));
+ return;
+ }
+
+ int32_t call_id = GetCallbackId();
+ args.GetReturnValue().Set(call_id);
+
+ auto on_call_completed =
+ base::Bind(&DisplaySourceCustomBindings::OnCallCompleted,
+ weak_factory_.GetWeakPtr(), call_id);
+ // The session will get removed from session_map_ in OnSessionTerminated.
+ session->Terminate(on_call_completed);
+}
+
+void DisplaySourceCustomBindings::OnCallCompleted(
+ int call_id,
+ bool success,
+ const std::string& error_message) {
+ v8::Isolate* isolate = context()->isolate();
+ ModuleSystem* module_system = context()->module_system();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context()->v8_context());
+
+ v8::Local<v8::Value> callback_args[2];
+ callback_args[0] = v8::Integer::New(isolate, call_id);
+ if (success)
+ callback_args[1] = v8::Null(isolate);
+ else
+ callback_args[1] = v8::String::NewFromUtf8(isolate, error_message.c_str());
+
+ module_system->CallModuleMethod("displaySource", "callCompletionCallback", 2,
+ callback_args);
+}
+
+void DisplaySourceCustomBindings::OnSessionStarted(
+ int sink_id,
+ int call_id,
+ bool success,
+ const std::string& error_message) {
+ CHECK(GetDisplaySession(sink_id));
+ if (!success) {
+ // Session has failed to start, removing it.
+ session_map_.erase(sink_id);
+ }
+ OnCallCompleted(call_id, success, error_message);
+}
+
+void DisplaySourceCustomBindings::DispatchSessionTerminated(int sink_id) const {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context()->v8_context());
+ v8::Local<v8::Array> event_args = v8::Array::New(isolate, 1);
+ event_args->Set(0, v8::Integer::New(isolate, sink_id));
+ context()->DispatchEvent("displaySource.onSessionTerminated", event_args);
+}
+
+void DisplaySourceCustomBindings::DispatchSessionError(
+ int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& message) const {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context()->v8_context());
+
+ api::display_source::ErrorInfo error_info;
+ error_info.type = type;
+ if (!message.empty())
+ error_info.description.reset(new std::string(message));
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ v8::Local<v8::Value> info_arg =
+ converter->ToV8Value(error_info.ToValue().get(),
+ context()->v8_context());
+
+ v8::Local<v8::Array> event_args = v8::Array::New(isolate, 2);
+ event_args->Set(0, v8::Integer::New(isolate, sink_id));
+ event_args->Set(1, info_arg);
+ context()->DispatchEvent("displaySource.onSessionErrorOccured", event_args);
+}
+
+DisplaySourceSession* DisplaySourceCustomBindings::GetDisplaySession(
+ int sink_id) const {
+ auto iter = session_map_.find(sink_id);
+ if (iter != session_map_.end())
+ return iter->second.get();
+ return nullptr;
+}
+
+void DisplaySourceCustomBindings::OnSessionTerminated(int sink_id) {
+ CHECK(GetDisplaySession(sink_id));
+ session_map_.erase(sink_id);
+ DispatchSessionTerminated(sink_id);
+}
+
+void DisplaySourceCustomBindings::OnSessionError(int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& message) {
+ CHECK(GetDisplaySession(sink_id));
+ DispatchSessionError(sink_id, type, message);
+}
+
+} // extensions
diff --git a/chromium/extensions/renderer/display_source_custom_bindings.h b/chromium/extensions/renderer/display_source_custom_bindings.h
new file mode 100644
index 00000000000..b7e5e967ac7
--- /dev/null
+++ b/chromium/extensions/renderer/display_source_custom_bindings.h
@@ -0,0 +1,63 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_DISPLAY_SOURCE_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_DISPLAY_SOURCE_CUSTOM_BINDINGS_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/api/display_source.h"
+#include "extensions/renderer/api/display_source/display_source_session.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Implements custom bindings for the displaySource API.
+class DisplaySourceCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit DisplaySourceCustomBindings(ScriptContext* context);
+
+ ~DisplaySourceCustomBindings() override;
+
+ private:
+ // ObjectBackedNativeHandler override.
+ void Invalidate() override;
+
+ void StartSession(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ void TerminateSession(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ // Call completion callbacks.
+ void OnCallCompleted(int call_id,
+ bool success,
+ const std::string& error_message);
+ void OnSessionStarted(int sink_id,
+ int call_id,
+ bool success,
+ const std::string& error_message);
+ // Dispatch events
+ void DispatchSessionTerminated(int sink_id) const;
+ void DispatchSessionError(int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& message) const;
+
+ // DisplaySession notification callbacks.
+ void OnSessionTerminated(int sink_id);
+ void OnSessionError(int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& message);
+
+ DisplaySourceSession* GetDisplaySession(int sink_id) const;
+
+ std::map<int, scoped_ptr<DisplaySourceSession>> session_map_;
+ base::WeakPtrFactory<DisplaySourceCustomBindings> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceCustomBindings);
+};
+
+} // extensions
+
+#endif // EXTENSIONS_RENDERER_DISPLAY_SOURCE_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/document_custom_bindings.cc b/chromium/extensions/renderer/document_custom_bindings.cc
new file mode 100644
index 00000000000..5f81ee8e1b2
--- /dev/null
+++ b/chromium/extensions/renderer/document_custom_bindings.cc
@@ -0,0 +1,42 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/document_custom_bindings.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+DocumentCustomBindings::DocumentCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("RegisterElement",
+ base::Bind(&DocumentCustomBindings::RegisterElement,
+ base::Unretained(this)));
+}
+
+// Attach an event name to an object.
+void DocumentCustomBindings::RegisterElement(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 2 || !args[0]->IsString() || !args[1]->IsObject()) {
+ NOTREACHED();
+ return;
+ }
+
+ std::string element_name(*v8::String::Utf8Value(args[0]));
+ v8::Local<v8::Object> options = v8::Local<v8::Object>::Cast(args[1]);
+
+ blink::WebExceptionCode ec = 0;
+ blink::WebDocument document = context()->web_frame()->document();
+ v8::Local<v8::Value> constructor = document.registerEmbedderCustomElement(
+ blink::WebString::fromUTF8(element_name), options, ec);
+ args.GetReturnValue().Set(constructor);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/document_custom_bindings.h b/chromium/extensions/renderer/document_custom_bindings.h
new file mode 100644
index 00000000000..6c07eab0fa2
--- /dev/null
+++ b/chromium/extensions/renderer/document_custom_bindings.h
@@ -0,0 +1,25 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_DOCUMENT_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_DOCUMENT_CUSTOM_BINDINGS_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Implements custom bindings for document-level operations.
+class DocumentCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ DocumentCustomBindings(ScriptContext* context);
+
+ private:
+ // Registers the provided element as a custom element in Blink.
+ void RegisterElement(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_DOCUMENT_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/dom_activity_logger.cc b/chromium/extensions/renderer/dom_activity_logger.cc
new file mode 100644
index 00000000000..92055232c71
--- /dev/null
+++ b/chromium/extensions/renderer/dom_activity_logger.cc
@@ -0,0 +1,133 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/dom_activity_logger.h"
+
+#include <utility>
+
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/dom_action_types.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/activity_log_converter_strategy.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURL.h"
+
+using content::V8ValueConverter;
+using blink::WebString;
+using blink::WebURL;
+
+namespace extensions {
+
+namespace {
+
+// Converts the given |v8_value| and appends it to the given |list|, if the
+// conversion succeeds.
+void AppendV8Value(const std::string& api_name,
+ const v8::Local<v8::Value>& v8_value,
+ base::ListValue* list) {
+ DCHECK(list);
+ std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ ActivityLogConverterStrategy strategy;
+ converter->SetFunctionAllowed(true);
+ converter->SetStrategy(&strategy);
+ std::unique_ptr<base::Value> value(converter->FromV8Value(
+ v8_value, v8::Isolate::GetCurrent()->GetCurrentContext()));
+
+ if (value.get())
+ list->Append(value.release());
+}
+
+} // namespace
+
+DOMActivityLogger::DOMActivityLogger(const std::string& extension_id)
+ : extension_id_(extension_id) {
+}
+
+DOMActivityLogger::~DOMActivityLogger() {}
+
+void DOMActivityLogger::AttachToWorld(int world_id,
+ const std::string& extension_id) {
+ // If there is no logger registered for world_id, construct a new logger
+ // and register it with world_id.
+ if (!blink::hasDOMActivityLogger(world_id,
+ WebString::fromUTF8(extension_id))) {
+ DOMActivityLogger* logger = new DOMActivityLogger(extension_id);
+ blink::setDOMActivityLogger(world_id,
+ WebString::fromUTF8(extension_id),
+ logger);
+ }
+}
+
+void DOMActivityLogger::logGetter(const WebString& api_name,
+ const WebURL& url,
+ const WebString& title) {
+ SendDomActionMessage(api_name.utf8(), url, title, DomActionType::GETTER,
+ std::unique_ptr<base::ListValue>(new base::ListValue()));
+}
+
+void DOMActivityLogger::logSetter(const WebString& api_name,
+ const v8::Local<v8::Value>& new_value,
+ const WebURL& url,
+ const WebString& title) {
+ logSetter(api_name, new_value, v8::Local<v8::Value>(), url, title);
+}
+
+void DOMActivityLogger::logSetter(const WebString& api_name,
+ const v8::Local<v8::Value>& new_value,
+ const v8::Local<v8::Value>& old_value,
+ const WebURL& url,
+ const WebString& title) {
+ std::unique_ptr<base::ListValue> args(new base::ListValue);
+ std::string api_name_utf8 = api_name.utf8();
+ AppendV8Value(api_name_utf8, new_value, args.get());
+ if (!old_value.IsEmpty())
+ AppendV8Value(api_name_utf8, old_value, args.get());
+ SendDomActionMessage(api_name_utf8, url, title, DomActionType::SETTER,
+ std::move(args));
+}
+
+void DOMActivityLogger::logMethod(const WebString& api_name,
+ int argc,
+ const v8::Local<v8::Value>* argv,
+ const WebURL& url,
+ const WebString& title) {
+ std::unique_ptr<base::ListValue> args(new base::ListValue);
+ std::string api_name_utf8 = api_name.utf8();
+ for (int i = 0; i < argc; ++i)
+ AppendV8Value(api_name_utf8, argv[i], args.get());
+ SendDomActionMessage(api_name_utf8, url, title, DomActionType::METHOD,
+ std::move(args));
+}
+
+void DOMActivityLogger::logEvent(const WebString& event_name,
+ int argc,
+ const WebString* argv,
+ const WebURL& url,
+ const WebString& title) {
+ std::unique_ptr<base::ListValue> args(new base::ListValue);
+ std::string event_name_utf8 = event_name.utf8();
+ for (int i = 0; i < argc; ++i)
+ args->Append(new base::StringValue(argv[i]));
+ SendDomActionMessage(event_name_utf8, url, title, DomActionType::METHOD,
+ std::move(args));
+}
+
+void DOMActivityLogger::SendDomActionMessage(
+ const std::string& api_call,
+ const GURL& url,
+ const base::string16& url_title,
+ DomActionType::Type call_type,
+ std::unique_ptr<base::ListValue> args) {
+ ExtensionHostMsg_DOMAction_Params params;
+ params.api_call = api_call;
+ params.url = url;
+ params.url_title = url_title;
+ params.call_type = call_type;
+ params.arguments.Swap(args.get());
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_AddDOMActionToActivityLog(extension_id_, params));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/dom_activity_logger.h b/chromium/extensions/renderer/dom_activity_logger.h
new file mode 100644
index 00000000000..3956c4847b4
--- /dev/null
+++ b/chromium/extensions/renderer/dom_activity_logger.h
@@ -0,0 +1,91 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_DOM_ACTIVITY_LOGGER_H_
+#define EXTENSIONS_RENDERER_DOM_ACTIVITY_LOGGER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/dom_action_types.h"
+#include "third_party/WebKit/public/web/WebDOMActivityLogger.h"
+#include "v8/include/v8.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace blink {
+class WebString;
+class WebURL;
+}
+
+namespace content {
+class V8ValueConverter;
+}
+
+namespace extensions {
+
+// Used to log DOM API calls from within WebKit. The events are sent via IPC to
+// extensions::ActivityLog for recording and display.
+class DOMActivityLogger: public blink::WebDOMActivityLogger {
+ public:
+ static const int kMainWorldId = 0;
+ explicit DOMActivityLogger(const std::string& extension_id);
+ ~DOMActivityLogger() override;
+
+ // Check (using the WebKit API) if there is no logger attached to the world
+ // corresponding to world_id, and if so, construct a new logger and attach it.
+ // world_id = 0 indicates the main world.
+ static void AttachToWorld(int world_id,
+ const std::string& extension_id);
+
+ private:
+ // blink::WebDOMActivityLogger implementation.
+ // Marshals the arguments into an ExtensionHostMsg_DOMAction_Params and sends
+ // it over to the browser (via IPC) for appending it to the extension activity
+ // log.
+ // These methods don't have the override keyword due to the complexities it
+ // introduces when changes blink apis.
+ void logGetter(const blink::WebString& api_name,
+ const blink::WebURL& url,
+ const blink::WebString& title) override;
+ void logSetter(const blink::WebString& api_name,
+ const v8::Local<v8::Value>& new_value,
+ const blink::WebURL& url,
+ const blink::WebString& title) override;
+ virtual void logSetter(const blink::WebString& api_name,
+ const v8::Local<v8::Value>& new_value,
+ const v8::Local<v8::Value>& old_value,
+ const blink::WebURL& url,
+ const blink::WebString& title);
+ void logMethod(const blink::WebString& api_name,
+ int argc,
+ const v8::Local<v8::Value>* argv,
+ const blink::WebURL& url,
+ const blink::WebString& title) override;
+ void logEvent(const blink::WebString& event_name,
+ int argc,
+ const blink::WebString* argv,
+ const blink::WebURL& url,
+ const blink::WebString& title) override;
+
+ // Helper function to actually send the message across IPC.
+ void SendDomActionMessage(const std::string& api_call,
+ const GURL& url,
+ const base::string16& url_title,
+ DomActionType::Type call_type,
+ std::unique_ptr<base::ListValue> args);
+
+ // The id of the extension with which this logger is associated.
+ std::string extension_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(DOMActivityLogger);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_DOM_ACTIVITY_LOGGER_H_
+
diff --git a/chromium/extensions/renderer/event_bindings.cc b/chromium/extensions/renderer/event_bindings.cc
new file mode 100644
index 00000000000..87b8349d098
--- /dev/null
+++ b/chromium/extensions/renderer/event_bindings.cc
@@ -0,0 +1,367 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/event_bindings.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/crx_file/id_util.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/event_filter.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/value_counter.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/script_context.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+// A map of event names to the number of contexts listening to that event.
+// We notify the browser about event listeners when we transition between 0
+// and 1.
+typedef std::map<std::string, int> EventListenerCounts;
+
+// A map of extension IDs to listener counts for that extension.
+base::LazyInstance<std::map<std::string, EventListenerCounts>>
+ g_listener_counts = LAZY_INSTANCE_INITIALIZER;
+
+// A map of (extension ID, event name) pairs to the filtered listener counts
+// for that pair. The map is used to keep track of which filters are in effect
+// for which events. We notify the browser about filtered event listeners when
+// we transition between 0 and 1.
+using FilteredEventListenerKey = std::pair<std::string, std::string>;
+using FilteredEventListenerCounts =
+ std::map<FilteredEventListenerKey, scoped_ptr<ValueCounter>>;
+base::LazyInstance<FilteredEventListenerCounts> g_filtered_listener_counts =
+ LAZY_INSTANCE_INITIALIZER;
+
+base::LazyInstance<EventFilter> g_event_filter = LAZY_INSTANCE_INITIALIZER;
+
+// Gets a unique string key identifier for a ScriptContext.
+// TODO(kalman): Just use pointer equality...?
+std::string GetKeyForScriptContext(ScriptContext* script_context) {
+ const std::string& extension_id = script_context->GetExtensionID();
+ CHECK(crx_file::id_util::IdIsValid(extension_id) ||
+ script_context->url().is_valid());
+ return crx_file::id_util::IdIsValid(extension_id)
+ ? extension_id
+ : script_context->url().spec();
+}
+
+// Increments the number of event-listeners for the given |event_name| and
+// ScriptContext. Returns the count after the increment.
+int IncrementEventListenerCount(ScriptContext* script_context,
+ const std::string& event_name) {
+ return ++g_listener_counts
+ .Get()[GetKeyForScriptContext(script_context)][event_name];
+}
+
+// Decrements the number of event-listeners for the given |event_name| and
+// ScriptContext. Returns the count after the increment.
+int DecrementEventListenerCount(ScriptContext* script_context,
+ const std::string& event_name) {
+ return --g_listener_counts
+ .Get()[GetKeyForScriptContext(script_context)][event_name];
+}
+
+EventFilteringInfo ParseFromObject(v8::Local<v8::Object> object,
+ v8::Isolate* isolate) {
+ EventFilteringInfo info;
+ v8::Local<v8::String> url(v8::String::NewFromUtf8(isolate, "url"));
+ if (object->Has(url)) {
+ v8::Local<v8::Value> url_value(object->Get(url));
+ info.SetURL(GURL(*v8::String::Utf8Value(url_value)));
+ }
+ v8::Local<v8::String> instance_id(
+ v8::String::NewFromUtf8(isolate, "instanceId"));
+ if (object->Has(instance_id)) {
+ v8::Local<v8::Value> instance_id_value(object->Get(instance_id));
+ info.SetInstanceID(instance_id_value->IntegerValue());
+ }
+ v8::Local<v8::String> service_type(
+ v8::String::NewFromUtf8(isolate, "serviceType"));
+ if (object->Has(service_type)) {
+ v8::Local<v8::Value> service_type_value(object->Get(service_type));
+ info.SetServiceType(*v8::String::Utf8Value(service_type_value));
+ }
+ v8::Local<v8::String> window_types(
+ v8::String::NewFromUtf8(isolate, "windowType"));
+ if (object->Has(window_types)) {
+ v8::Local<v8::Value> window_types_value(object->Get(window_types));
+ info.SetWindowType(*v8::String::Utf8Value(window_types_value));
+ }
+
+ v8::Local<v8::String> window_exposed(
+ v8::String::NewFromUtf8(isolate, "windowExposedByDefault"));
+ if (object->Has(window_exposed)) {
+ v8::Local<v8::Value> window_exposed_value(object->Get(window_exposed));
+ info.SetWindowExposedByDefault(
+ window_exposed_value.As<v8::Boolean>()->Value());
+ }
+
+ return info;
+}
+
+// Add a filter to |event_name| in |extension_id|, returning true if it
+// was the first filter for that event in that extension.
+bool AddFilter(const std::string& event_name,
+ const std::string& extension_id,
+ const base::DictionaryValue& filter) {
+ FilteredEventListenerKey key(extension_id, event_name);
+ FilteredEventListenerCounts& all_counts = g_filtered_listener_counts.Get();
+ FilteredEventListenerCounts::const_iterator counts = all_counts.find(key);
+ if (counts == all_counts.end()) {
+ counts = all_counts.insert(std::make_pair(
+ key, make_scoped_ptr(new ValueCounter())))
+ .first;
+ }
+ return counts->second->Add(filter);
+}
+
+// Remove a filter from |event_name| in |extension_id|, returning true if it
+// was the last filter for that event in that extension.
+bool RemoveFilter(const std::string& event_name,
+ const std::string& extension_id,
+ base::DictionaryValue* filter) {
+ FilteredEventListenerKey key(extension_id, event_name);
+ FilteredEventListenerCounts& all_counts = g_filtered_listener_counts.Get();
+ FilteredEventListenerCounts::const_iterator counts = all_counts.find(key);
+ if (counts == all_counts.end())
+ return false;
+ // Note: Remove() returns true if it removed the last filter equivalent to
+ // |filter|. If there are more equivalent filters, or if there weren't any in
+ // the first place, it returns false.
+ if (counts->second->Remove(*filter)) {
+ if (counts->second->is_empty())
+ all_counts.erase(counts); // Clean up if there are no more filters.
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+EventBindings::EventBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("AttachEvent", base::Bind(&EventBindings::AttachEventHandler,
+ base::Unretained(this)));
+ RouteFunction("DetachEvent", base::Bind(&EventBindings::DetachEventHandler,
+ base::Unretained(this)));
+ RouteFunction(
+ "AttachFilteredEvent",
+ base::Bind(&EventBindings::AttachFilteredEvent, base::Unretained(this)));
+ RouteFunction("DetachFilteredEvent",
+ base::Bind(&EventBindings::DetachFilteredEventHandler,
+ base::Unretained(this)));
+ RouteFunction("MatchAgainstEventFilter",
+ base::Bind(&EventBindings::MatchAgainstEventFilter,
+ base::Unretained(this)));
+
+ // It's safe to use base::Unretained here because |context| will always
+ // outlive us.
+ context->AddInvalidationObserver(
+ base::Bind(&EventBindings::OnInvalidated, base::Unretained(this)));
+}
+
+EventBindings::~EventBindings() {}
+
+void EventBindings::AttachEventHandler(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsString());
+ AttachEvent(*v8::String::Utf8Value(args[0]));
+}
+
+void EventBindings::AttachEvent(const std::string& event_name) {
+ if (!context()->HasAccessOrThrowError(event_name))
+ return;
+
+ // Record the attachment for this context so that events can be detached when
+ // the context is destroyed.
+ //
+ // Ideally we'd CHECK that it's not already attached, however that's not
+ // possible because extensions can create and attach events themselves. Very
+ // silly, but that's the way it is. For an example of this, see
+ // chrome/test/data/extensions/api_test/events/background.js.
+ attached_event_names_.insert(event_name);
+
+ const std::string& extension_id = context()->GetExtensionID();
+ if (IncrementEventListenerCount(context(), event_name) == 1) {
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_AddListener(
+ extension_id, context()->url(), event_name));
+ }
+
+ // This is called the first time the page has added a listener. Since
+ // the background page is the only lazy page, we know this is the first
+ // time this listener has been registered.
+ if (ExtensionFrameHelper::IsContextForEventPage(context())) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_AddLazyListener(extension_id, event_name));
+ }
+}
+
+void EventBindings::DetachEventHandler(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsBoolean());
+ DetachEvent(*v8::String::Utf8Value(args[0]), args[1]->BooleanValue());
+}
+
+void EventBindings::DetachEvent(const std::string& event_name, bool is_manual) {
+ // See comment in AttachEvent().
+ attached_event_names_.erase(event_name);
+
+ const std::string& extension_id = context()->GetExtensionID();
+
+ if (DecrementEventListenerCount(context(), event_name) == 0) {
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_RemoveListener(
+ extension_id, context()->url(), event_name));
+ }
+
+ // DetachEvent is called when the last listener for the context is
+ // removed. If the context is the background page, and it removes the
+ // last listener manually, then we assume that it is no longer interested
+ // in being awakened for this event.
+ if (is_manual && ExtensionFrameHelper::IsContextForEventPage(context())) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_RemoveLazyListener(extension_id, event_name));
+ }
+}
+
+// MatcherID AttachFilteredEvent(string event_name, object filter)
+// event_name - Name of the event to attach.
+// filter - Which instances of the named event are we interested in.
+// returns the id assigned to the listener, which will be returned from calls
+// to MatchAgainstEventFilter where this listener matches.
+void EventBindings::AttachFilteredEvent(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsObject());
+
+ std::string event_name = *v8::String::Utf8Value(args[0]);
+ if (!context()->HasAccessOrThrowError(event_name))
+ return;
+
+ scoped_ptr<base::DictionaryValue> filter;
+ {
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ scoped_ptr<base::Value> filter_value(converter->FromV8Value(
+ v8::Local<v8::Object>::Cast(args[1]), context()->v8_context()));
+ if (!filter_value || !filter_value->IsType(base::Value::TYPE_DICTIONARY)) {
+ args.GetReturnValue().Set(static_cast<int32_t>(-1));
+ return;
+ }
+ filter = base::DictionaryValue::From(std::move(filter_value));
+ }
+
+ // Hold onto a weak reference to |filter| so that it can be used after passing
+ // ownership to |event_filter|.
+ base::DictionaryValue* filter_weak = filter.get();
+ int id = g_event_filter.Get().AddEventMatcher(
+ event_name, ParseEventMatcher(std::move(filter)));
+ attached_matcher_ids_.insert(id);
+
+ // Only send IPCs the first time a filter gets added.
+ std::string extension_id = context()->GetExtensionID();
+ if (AddFilter(event_name, extension_id, *filter_weak)) {
+ bool lazy = ExtensionFrameHelper::IsContextForEventPage(context());
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_AddFilteredListener(
+ extension_id, event_name, *filter_weak, lazy));
+ }
+
+ args.GetReturnValue().Set(static_cast<int32_t>(id));
+}
+
+void EventBindings::DetachFilteredEventHandler(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsBoolean());
+ DetachFilteredEvent(args[0]->Int32Value(), args[1]->BooleanValue());
+}
+
+void EventBindings::DetachFilteredEvent(int matcher_id, bool is_manual) {
+ EventFilter& event_filter = g_event_filter.Get();
+ EventMatcher* event_matcher = event_filter.GetEventMatcher(matcher_id);
+
+ const std::string& event_name = event_filter.GetEventName(matcher_id);
+
+ // Only send IPCs the last time a filter gets removed.
+ std::string extension_id = context()->GetExtensionID();
+ if (RemoveFilter(event_name, extension_id, event_matcher->value())) {
+ bool remove_lazy =
+ is_manual && ExtensionFrameHelper::IsContextForEventPage(context());
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_RemoveFilteredListener(
+ extension_id, event_name, *event_matcher->value(), remove_lazy));
+ }
+
+ event_filter.RemoveEventMatcher(matcher_id);
+ attached_matcher_ids_.erase(matcher_id);
+}
+
+void EventBindings::MatchAgainstEventFilter(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ typedef std::set<EventFilter::MatcherID> MatcherIDs;
+ EventFilter& event_filter = g_event_filter.Get();
+ std::string event_name = *v8::String::Utf8Value(args[0]);
+ EventFilteringInfo info =
+ ParseFromObject(args[1]->ToObject(isolate), isolate);
+ // Only match events routed to this context's RenderFrame or ones that don't
+ // have a routingId in their filter.
+ MatcherIDs matched_event_filters = event_filter.MatchEvent(
+ event_name, info, context()->GetRenderFrame()->GetRoutingID());
+ v8::Local<v8::Array> array(
+ v8::Array::New(isolate, matched_event_filters.size()));
+ int i = 0;
+ for (MatcherIDs::iterator it = matched_event_filters.begin();
+ it != matched_event_filters.end();
+ ++it) {
+ array->Set(v8::Integer::New(isolate, i++), v8::Integer::New(isolate, *it));
+ }
+ args.GetReturnValue().Set(array);
+}
+
+scoped_ptr<EventMatcher> EventBindings::ParseEventMatcher(
+ scoped_ptr<base::DictionaryValue> filter) {
+ return make_scoped_ptr(new EventMatcher(
+ std::move(filter), context()->GetRenderFrame()->GetRoutingID()));
+}
+
+void EventBindings::OnInvalidated() {
+ // Detach all attached events that weren't attached. Iterate over a copy
+ // because it will be mutated.
+ std::set<std::string> attached_event_names_safe = attached_event_names_;
+ for (const std::string& event_name : attached_event_names_safe) {
+ DetachEvent(event_name, false /* is_manual */);
+ }
+ DCHECK(attached_event_names_.empty())
+ << "Events cannot be attached during invalidation";
+
+ // Same for filtered events.
+ std::set<int> attached_matcher_ids_safe = attached_matcher_ids_;
+ for (int matcher_id : attached_matcher_ids_safe) {
+ DetachFilteredEvent(matcher_id, false /* is_manual */);
+ }
+ DCHECK(attached_matcher_ids_.empty())
+ << "Filtered events cannot be attached during invalidation";
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/event_bindings.h b/chromium/extensions/renderer/event_bindings.h
new file mode 100644
index 00000000000..8c179983717
--- /dev/null
+++ b/chromium/extensions/renderer/event_bindings.h
@@ -0,0 +1,89 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_EVENT_BINDINGS_H_
+#define EXTENSIONS_RENDERER_EVENT_BINDINGS_H_
+
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+class EventMatcher;
+
+// This class deals with the javascript bindings related to Event objects.
+class EventBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit EventBindings(ScriptContext* context);
+ ~EventBindings() override;
+
+ private:
+ // JavaScript handler which forwards to AttachEvent().
+ // args[0] forwards to |event_name|.
+ void AttachEventHandler(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Attach an event name to an object.
+ // |event_name| The name of the event to attach.
+ void AttachEvent(const std::string& event_name);
+
+ // JavaScript handler which forwards to DetachEvent().
+ // args[0] forwards to |event_name|.
+ // args[1] forwards to |is_manual|.
+ void DetachEventHandler(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Detaches an event name from an object.
+ // |event_name| The name of the event to stop listening to.
+ // |is_manual| True if this detach was done by the user via removeListener()
+ // as opposed to automatically during shutdown, in which case we should inform
+ // the browser we are no longer interested in that event.
+ void DetachEvent(const std::string& event_name, bool is_manual);
+
+ // MatcherID AttachFilteredEvent(string event_name, object filter)
+ // |event_name| Name of the event to attach.
+ // |filter| Which instances of the named event are we interested in.
+ // returns the id assigned to the listener, which will be returned from calls
+ // to MatchAgainstEventFilter where this listener matches.
+ void AttachFilteredEvent(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // JavaScript handler which forwards to DetachFilteredEvent.
+ // void DetachFilteredEvent(int id, bool manual)
+ // args[0] forwards to |matcher_id|
+ // args[1] forwards to |is_manual|
+ void DetachFilteredEventHandler(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Detaches a filtered event. Unlike a normal event, a filtered event is
+ // identified by a unique ID per filter, not its name.
+ // |matcher_id| The ID of the filtered event.
+ // |is_manual| false if this is part of the extension unload process where all
+ // listeners are automatically detached.
+ void DetachFilteredEvent(int matcher_id, bool is_manual);
+
+ void MatchAgainstEventFilter(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ scoped_ptr<EventMatcher> ParseEventMatcher(
+ scoped_ptr<base::DictionaryValue> filter);
+
+ // Called when our context, and therefore us, is invalidated. Run any cleanup.
+ void OnInvalidated();
+
+ // The set of attached events and filtered events. Maintain these so that we
+ // can detch them on unload.
+ std::set<std::string> attached_event_names_;
+ std::set<int> attached_matcher_ids_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventBindings);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EVENT_BINDINGS_H_
diff --git a/chromium/extensions/renderer/event_unittest.cc b/chromium/extensions/renderer/event_unittest.cc
new file mode 100644
index 00000000000..59694623dca
--- /dev/null
+++ b/chromium/extensions/renderer/event_unittest.cc
@@ -0,0 +1,259 @@
+// Copyright 2014 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.
+
+#include "extensions/common/extension_urls.h"
+#include "extensions/renderer/module_system_test.h"
+#include "grit/extensions_renderer_resources.h"
+
+namespace extensions {
+namespace {
+
+class EventUnittest : public ModuleSystemTest {
+ void SetUp() override {
+ ModuleSystemTest::SetUp();
+
+ env()->RegisterModule(kEventBindings, IDR_EVENT_BINDINGS_JS);
+ env()->RegisterModule("json_schema", IDR_JSON_SCHEMA_JS);
+ env()->RegisterModule(kSchemaUtils, IDR_SCHEMA_UTILS_JS);
+ env()->RegisterModule("uncaught_exception_handler",
+ IDR_UNCAUGHT_EXCEPTION_HANDLER_JS);
+ env()->RegisterModule("utils", IDR_UTILS_JS);
+
+ // Mock out the native handler for event_bindings. These mocks will fail if
+ // any invariants maintained by the real event_bindings are broken.
+ env()->OverrideNativeHandler(
+ "event_natives",
+ "var assert = requireNative('assert');"
+ "exports.$set('attachedListeners', {});"
+ "var attachedListeners = exports.attachedListeners;"
+ "exports.$set('attachedFilteredListeners', {});"
+ "var attachedFilteredListeners = exports.attachedFilteredListeners;"
+ "var nextId = 0;"
+ "var idToName = {};"
+ "exports.$set('AttachEvent', function(eventName) {"
+ " assert.AssertFalse(!!attachedListeners[eventName]);"
+ " attachedListeners[eventName] = 1;"
+ "});"
+ "exports.$set('DetachEvent', function(eventName) {"
+ " assert.AssertTrue(!!attachedListeners[eventName]);"
+ " delete attachedListeners[eventName];"
+ "});"
+ "exports.$set('IsEventAttached', function(eventName) {"
+ " return !!attachedListeners[eventName];"
+ "});"
+ "exports.$set('AttachFilteredEvent', function(name, filters) {"
+ " var id = nextId++;"
+ " idToName[id] = name;"
+ " attachedFilteredListeners[name] ="
+ " attachedFilteredListeners[name] || [];"
+ " attachedFilteredListeners[name][id] = filters;"
+ " return id;"
+ "});"
+ "exports.$set('DetachFilteredEvent', function(id, manual) {"
+ " var i = attachedFilteredListeners[idToName[id]].indexOf(id);"
+ " attachedFilteredListeners[idToName[id]].splice(i, 1);"
+ "});"
+ "exports.$set('HasFilteredListener', function(name) {"
+ " return attachedFilteredListeners[name].length;"
+ "});");
+ env()->OverrideNativeHandler("sendRequest",
+ "exports.$set('sendRequest', function() {});");
+ env()->OverrideNativeHandler(
+ "apiDefinitions",
+ "exports.$set('GetExtensionAPIDefinitionsForTest', function() {});");
+ env()->OverrideNativeHandler("logging",
+ "exports.$set('DCHECK', function() {});");
+ env()->OverrideNativeHandler("schema_registry",
+ "exports.$set('GetSchema', function() {});");
+ }
+};
+
+TEST_F(EventUnittest, TestNothing) {
+ ExpectNoAssertionsMade();
+}
+
+TEST_F(EventUnittest, AddRemoveTwoListeners) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var assert = requireNative('assert');"
+ "var Event = require('event_bindings').Event;"
+ "var eventNatives = requireNative('event_natives');"
+ "var myEvent = new Event('named-event');"
+ "var cb1 = function() {};"
+ "var cb2 = function() {};"
+ "myEvent.addListener(cb1);"
+ "myEvent.addListener(cb2);"
+ "myEvent.removeListener(cb1);"
+ "assert.AssertTrue(!!eventNatives.attachedListeners['named-event']);"
+ "myEvent.removeListener(cb2);"
+ "assert.AssertFalse(!!eventNatives.attachedListeners['named-event']);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, EventsThatSupportRulesMustHaveAName) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var eventOpts = {supportsRules: true};"
+ "var assert = requireNative('assert');"
+ "var caught = false;"
+ "try {"
+ " var myEvent = new Event(undefined, undefined, eventOpts);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(caught);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, NamedEventDispatch) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var dispatchEvent = require('event_bindings').dispatchEvent;"
+ "var assert = requireNative('assert');"
+ "var e = new Event('myevent');"
+ "var called = false;"
+ "e.addListener(function() { called = true; });"
+ "dispatchEvent('myevent', []);"
+ "assert.AssertTrue(called);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, AddListenerWithFiltersThrowsErrorByDefault) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var e = new Event('myevent');"
+ "var filter = [{"
+ " url: {hostSuffix: 'google.com'},"
+ "}];"
+ "var caught = false;"
+ "try {"
+ " e.addListener(function() {}, filter);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(caught);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, FilteredEventsAttachment) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var bindings = requireNative('event_natives');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var filters = {url: [{hostSuffix: 'google.com'}]};"
+ "e.addListener(cb, filters);"
+ "assert.AssertTrue(bindings.HasFilteredListener('myevent'));"
+ "e.removeListener(cb);"
+ "assert.AssertFalse(bindings.HasFilteredListener('myevent'));");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, DetachFilteredEvent) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var bindings = requireNative('event_natives');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new Event('myevent', undefined, eventOpts);"
+ "var cb1 = function() {};"
+ "var cb2 = function() {};"
+ "var filters = {url: [{hostSuffix: 'google.com'}]};"
+ "e.addListener(cb1, filters);"
+ "e.addListener(cb2, filters);"
+ "privates(e).impl.detach_();"
+ "assert.AssertFalse(bindings.HasFilteredListener('myevent'));");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, AttachAndRemoveSameFilteredEventListener) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var bindings = requireNative('event_natives');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var filters = {url: [{hostSuffix: 'google.com'}]};"
+ "e.addListener(cb, filters);"
+ "e.addListener(cb, filters);"
+ "assert.AssertTrue(bindings.HasFilteredListener('myevent'));"
+ "e.removeListener(cb);"
+ "assert.AssertTrue(bindings.HasFilteredListener('myevent'));"
+ "e.removeListener(cb);"
+ "assert.AssertFalse(bindings.HasFilteredListener('myevent'));");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, AddingFilterWithUrlFieldNotAListThrowsException) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var filters = {url: {hostSuffix: 'google.com'}};"
+ "var caught = false;"
+ "try {"
+ " e.addListener(cb, filters);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(caught);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, MaxListeners) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var eventOpts = {supportsListeners: true, maxListeners: 1};"
+ "var e = new Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var caught = false;"
+ "try {"
+ " e.addListener(cb);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(!caught);"
+ "try {"
+ " e.addListener(cb);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(caught);");
+ env()->module_system()->Require("test");
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extension_frame_helper.cc b/chromium/extensions/renderer/extension_frame_helper.cc
new file mode 100644
index 00000000000..ba47a7c7488
--- /dev/null
+++ b/chromium/extensions/renderer/extension_frame_helper.cc
@@ -0,0 +1,277 @@
+// Copyright 2013 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.
+
+#include "extensions/renderer/extension_frame_helper.h"
+
+#include "base/strings/string_util.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/renderer/console.h"
+#include "extensions/renderer/content_watcher.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/messaging_bindings.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/platform/WebSecurityOrigin.h"
+#include "third_party/WebKit/public/web/WebConsoleMessage.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+namespace extensions {
+
+namespace {
+
+base::LazyInstance<std::set<const ExtensionFrameHelper*>> g_frame_helpers =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Returns true if the render frame corresponding with |frame_helper| matches
+// the given criteria.
+bool RenderFrameMatches(const ExtensionFrameHelper* frame_helper,
+ ViewType match_view_type,
+ int match_window_id,
+ const std::string& match_extension_id) {
+ if (match_view_type != VIEW_TYPE_INVALID &&
+ frame_helper->view_type() != match_view_type)
+ return false;
+
+ // Not all frames have a valid ViewType, e.g. devtools, most GuestViews, and
+ // unclassified detached WebContents.
+ if (frame_helper->view_type() == VIEW_TYPE_INVALID)
+ return false;
+
+ // This logic matches ExtensionWebContentsObserver::GetExtensionFromFrame.
+ blink::WebSecurityOrigin origin =
+ frame_helper->render_frame()->GetWebFrame()->getSecurityOrigin();
+ if (origin.isUnique() ||
+ !base::EqualsASCII(base::StringPiece16(origin.protocol()),
+ kExtensionScheme) ||
+ !base::EqualsASCII(base::StringPiece16(origin.host()),
+ match_extension_id.c_str()))
+ return false;
+
+ if (match_window_id != extension_misc::kUnknownWindowId &&
+ frame_helper->browser_window_id() != match_window_id)
+ return false;
+ return true;
+}
+
+// Runs every callback in |callbacks_to_be_run_and_cleared| while |frame_helper|
+// is valid, and clears |callbacks_to_be_run_and_cleared|.
+void RunCallbacksWhileFrameIsValid(
+ base::WeakPtr<ExtensionFrameHelper> frame_helper,
+ std::vector<base::Closure>* callbacks_to_be_run_and_cleared) {
+ // The JavaScript code can cause re-entrancy. To avoid a deadlock, don't run
+ // callbacks that are added during the iteration.
+ std::vector<base::Closure> callbacks;
+ callbacks_to_be_run_and_cleared->swap(callbacks);
+ for (auto& callback : callbacks) {
+ callback.Run();
+ if (!frame_helper.get())
+ return; // Frame and ExtensionFrameHelper invalidated by callback.
+ }
+}
+
+} // namespace
+
+ExtensionFrameHelper::ExtensionFrameHelper(content::RenderFrame* render_frame,
+ Dispatcher* extension_dispatcher)
+ : content::RenderFrameObserver(render_frame),
+ content::RenderFrameObserverTracker<ExtensionFrameHelper>(render_frame),
+ view_type_(VIEW_TYPE_INVALID),
+ tab_id_(-1),
+ browser_window_id_(-1),
+ extension_dispatcher_(extension_dispatcher),
+ did_create_current_document_element_(false),
+ weak_ptr_factory_(this) {
+ g_frame_helpers.Get().insert(this);
+}
+
+ExtensionFrameHelper::~ExtensionFrameHelper() {
+ g_frame_helpers.Get().erase(this);
+}
+
+// static
+std::vector<content::RenderFrame*> ExtensionFrameHelper::GetExtensionFrames(
+ const std::string& extension_id,
+ int browser_window_id,
+ ViewType view_type) {
+ std::vector<content::RenderFrame*> render_frames;
+ for (const ExtensionFrameHelper* helper : g_frame_helpers.Get()) {
+ if (RenderFrameMatches(helper, view_type, browser_window_id, extension_id))
+ render_frames.push_back(helper->render_frame());
+ }
+ return render_frames;
+}
+
+// static
+content::RenderFrame* ExtensionFrameHelper::GetBackgroundPageFrame(
+ const std::string& extension_id) {
+ for (const ExtensionFrameHelper* helper : g_frame_helpers.Get()) {
+ if (RenderFrameMatches(helper, VIEW_TYPE_EXTENSION_BACKGROUND_PAGE,
+ extension_misc::kUnknownWindowId, extension_id)) {
+ blink::WebLocalFrame* web_frame = helper->render_frame()->GetWebFrame();
+ // Check if this is the top frame.
+ if (web_frame->top() == web_frame)
+ return helper->render_frame();
+ }
+ }
+ return nullptr;
+}
+
+// static
+bool ExtensionFrameHelper::IsContextForEventPage(const ScriptContext* context) {
+ content::RenderFrame* render_frame = context->GetRenderFrame();
+ return context->extension() && render_frame &&
+ BackgroundInfo::HasLazyBackgroundPage(context->extension()) &&
+ ExtensionFrameHelper::Get(render_frame)->view_type() ==
+ VIEW_TYPE_EXTENSION_BACKGROUND_PAGE;
+}
+
+void ExtensionFrameHelper::DidCreateDocumentElement() {
+ did_create_current_document_element_ = true;
+ extension_dispatcher_->DidCreateDocumentElement(
+ render_frame()->GetWebFrame());
+}
+
+void ExtensionFrameHelper::DidCreateNewDocument() {
+ did_create_current_document_element_ = false;
+}
+
+void ExtensionFrameHelper::RunScriptsAtDocumentStart() {
+ DCHECK(did_create_current_document_element_);
+ RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
+ &document_element_created_callbacks_);
+ // |this| might be dead by now.
+}
+
+void ExtensionFrameHelper::RunScriptsAtDocumentEnd() {
+ RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
+ &document_load_finished_callbacks_);
+ // |this| might be dead by now.
+}
+
+void ExtensionFrameHelper::ScheduleAtDocumentStart(
+ const base::Closure& callback) {
+ document_element_created_callbacks_.push_back(callback);
+}
+
+void ExtensionFrameHelper::ScheduleAtDocumentEnd(
+ const base::Closure& callback) {
+ document_load_finished_callbacks_.push_back(callback);
+}
+
+void ExtensionFrameHelper::DidMatchCSS(
+ const blink::WebVector<blink::WebString>& newly_matching_selectors,
+ const blink::WebVector<blink::WebString>& stopped_matching_selectors) {
+ extension_dispatcher_->content_watcher()->DidMatchCSS(
+ render_frame()->GetWebFrame(), newly_matching_selectors,
+ stopped_matching_selectors);
+}
+
+void ExtensionFrameHelper::DidCreateScriptContext(
+ v8::Local<v8::Context> context,
+ int extension_group,
+ int world_id) {
+ extension_dispatcher_->DidCreateScriptContext(
+ render_frame()->GetWebFrame(), context, extension_group, world_id);
+}
+
+void ExtensionFrameHelper::WillReleaseScriptContext(
+ v8::Local<v8::Context> context,
+ int world_id) {
+ extension_dispatcher_->WillReleaseScriptContext(
+ render_frame()->GetWebFrame(), context, world_id);
+}
+
+bool ExtensionFrameHelper::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ExtensionFrameHelper, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnConnect,
+ OnExtensionDispatchOnConnect)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnExtensionDeliverMessage)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect,
+ OnExtensionDispatchOnDisconnect)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetTabId, OnExtensionSetTabId)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateBrowserWindowId,
+ OnUpdateBrowserWindowId)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_NotifyRenderViewType,
+ OnNotifyRendererViewType)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_Response, OnExtensionResponse)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnExtensionMessageInvoke)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ExtensionFrameHelper::OnExtensionDispatchOnConnect(
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id) {
+ MessagingBindings::DispatchOnConnect(
+ extension_dispatcher_->script_context_set(),
+ target_port_id,
+ channel_name,
+ source,
+ info,
+ tls_channel_id,
+ render_frame());
+}
+
+void ExtensionFrameHelper::OnExtensionDeliverMessage(int target_id,
+ const Message& message) {
+ MessagingBindings::DeliverMessage(
+ extension_dispatcher_->script_context_set(), target_id, message,
+ render_frame());
+}
+
+void ExtensionFrameHelper::OnExtensionDispatchOnDisconnect(
+ int port_id,
+ const std::string& error_message) {
+ MessagingBindings::DispatchOnDisconnect(
+ extension_dispatcher_->script_context_set(), port_id, error_message,
+ render_frame());
+}
+
+void ExtensionFrameHelper::OnExtensionSetTabId(int tab_id) {
+ CHECK_EQ(tab_id_, -1);
+ CHECK_GE(tab_id, 0);
+ tab_id_ = tab_id;
+}
+
+void ExtensionFrameHelper::OnUpdateBrowserWindowId(int browser_window_id) {
+ browser_window_id_ = browser_window_id;
+}
+
+void ExtensionFrameHelper::OnNotifyRendererViewType(ViewType type) {
+ // TODO(devlin): It'd be really nice to be able to
+ // DCHECK_EQ(VIEW_TYPE_INVALID, view_type_) here.
+ view_type_ = type;
+}
+
+void ExtensionFrameHelper::OnExtensionResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) {
+ extension_dispatcher_->OnExtensionResponse(request_id,
+ success,
+ response,
+ error);
+}
+
+void ExtensionFrameHelper::OnExtensionMessageInvoke(
+ const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture) {
+ extension_dispatcher_->InvokeModuleSystemMethod(render_frame(), extension_id,
+ module_name, function_name,
+ args, user_gesture);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extension_frame_helper.h b/chromium/extensions/renderer/extension_frame_helper.h
new file mode 100644
index 00000000000..57ac3b66b23
--- /dev/null
+++ b/chromium/extensions/renderer/extension_frame_helper.h
@@ -0,0 +1,148 @@
+// Copyright 2013 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.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSION_FRAME_HELPER_H_
+#define EXTENSIONS_RENDERER_EXTENSION_FRAME_HELPER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/common/console_message_level.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+#include "extensions/common/view_type.h"
+
+struct ExtensionMsg_ExternalConnectionInfo;
+struct ExtensionMsg_TabConnectionInfo;
+
+namespace base {
+class ListValue;
+}
+
+namespace extensions {
+
+class Dispatcher;
+struct Message;
+class ScriptContext;
+
+// RenderFrame-level plumbing for extension features.
+class ExtensionFrameHelper
+ : public content::RenderFrameObserver,
+ public content::RenderFrameObserverTracker<ExtensionFrameHelper> {
+ public:
+ ExtensionFrameHelper(content::RenderFrame* render_frame,
+ Dispatcher* extension_dispatcher);
+ ~ExtensionFrameHelper() override;
+
+ // Returns a list of extension RenderFrames that match the given filter
+ // criteria. A |browser_window_id| of extension_misc::kUnknownWindowId
+ // specifies "all", as does a |view_type| of VIEW_TYPE_INVALID.
+ static std::vector<content::RenderFrame*> GetExtensionFrames(
+ const std::string& extension_id,
+ int browser_window_id,
+ ViewType view_type);
+
+ // Returns the main frame of the extension's background page, or null if there
+ // isn't one in this process.
+ static content::RenderFrame* GetBackgroundPageFrame(
+ const std::string& extension_id);
+
+ // Returns true if the given |context| is for any frame in the extension's
+ // event page.
+ // TODO(devlin): This isn't really used properly, and should probably be
+ // deleted.
+ static bool IsContextForEventPage(const ScriptContext* context);
+
+ ViewType view_type() const { return view_type_; }
+ int tab_id() const { return tab_id_; }
+ int browser_window_id() const { return browser_window_id_; }
+ bool did_create_current_document_element() const {
+ return did_create_current_document_element_;
+ }
+
+ // Called when the document element has been inserted in this frame. This
+ // method may invoke untrusted JavaScript code that invalidate the frame and
+ // this ExtensionFrameHelper.
+ void RunScriptsAtDocumentStart();
+
+ // Called after the DOMContentLoaded event has fired.
+ void RunScriptsAtDocumentEnd();
+
+ // Schedule a callback, to be run at the next RunScriptsAtDocumentStart
+ // notification. Only call this when you are certain that there will be such a
+ // notification, e.g. from RenderFrameObserver::DidCreateDocumentElement.
+ // Otherwise the callback is never invoked, or invoked for a document that you
+ // were not expecting.
+ void ScheduleAtDocumentStart(const base::Closure& callback);
+
+ // Schedule a callback, to be run at the next RunScriptsAtDocumentEnd call.
+ void ScheduleAtDocumentEnd(const base::Closure& callback);
+
+ private:
+ // RenderFrameObserver implementation.
+ void DidCreateDocumentElement() override;
+ void DidCreateNewDocument() override;
+ void DidMatchCSS(
+ const blink::WebVector<blink::WebString>& newly_matching_selectors,
+ const blink::WebVector<blink::WebString>& stopped_matching_selectors)
+ override;
+ void DidCreateScriptContext(v8::Local<v8::Context>,
+ int extension_group,
+ int world_id) override;
+ void WillReleaseScriptContext(v8::Local<v8::Context>, int world_id) override;
+ bool OnMessageReceived(const IPC::Message& message) override;
+
+ // IPC handlers.
+ void OnExtensionDispatchOnConnect(
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id);
+ void OnExtensionDeliverMessage(int target_port_id,
+ const Message& message);
+ void OnExtensionDispatchOnDisconnect(int port_id,
+ const std::string& error_message);
+ void OnExtensionSetTabId(int tab_id);
+ void OnUpdateBrowserWindowId(int browser_window_id);
+ void OnNotifyRendererViewType(ViewType view_type);
+ void OnExtensionResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error);
+ void OnExtensionMessageInvoke(const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture);
+
+ // Type of view associated with the RenderFrame.
+ ViewType view_type_;
+
+ // The id of the tab the render frame is attached to.
+ int tab_id_;
+
+ // The id of the browser window the render frame is attached to.
+ int browser_window_id_;
+
+ Dispatcher* extension_dispatcher_;
+
+ // Whether or not the current document element has been created.
+ bool did_create_current_document_element_;
+
+ // Callbacks to be run at the next RunScriptsAtDocumentStart notification.
+ std::vector<base::Closure> document_element_created_callbacks_;
+
+ // Callbacks to be run at the next RunScriptsAtDocumentEnd notification.
+ std::vector<base::Closure> document_load_finished_callbacks_;
+
+ base::WeakPtrFactory<ExtensionFrameHelper> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionFrameHelper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EXTENSION_FRAME_HELPER_H_
diff --git a/chromium/extensions/renderer/extension_groups.h b/chromium/extensions/renderer/extension_groups.h
new file mode 100644
index 00000000000..9766fa4ed45
--- /dev/null
+++ b/chromium/extensions/renderer/extension_groups.h
@@ -0,0 +1,21 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSION_GROUPS_H_
+#define EXTENSIONS_RENDERER_EXTENSION_GROUPS_H_
+
+namespace extensions {
+
+// A set of extension groups for use with blink::registerExtension and
+// WebFrame::ExecuteScriptInNewWorld to control which extensions get loaded
+// into which contexts.
+// TODO(kalman): Remove this when https://crbug.com/481699 is fixed.
+enum ExtensionGroups {
+ // Use this to mark extensions to be loaded into content scripts only.
+ EXTENSION_GROUP_CONTENT_SCRIPTS = 1,
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EXTENSION_GROUPS_H_
diff --git a/chromium/extensions/renderer/extension_helper.cc b/chromium/extensions/renderer/extension_helper.cc
new file mode 100644
index 00000000000..4d3bd2711dc
--- /dev/null
+++ b/chromium/extensions/renderer/extension_helper.cc
@@ -0,0 +1,76 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/extension_helper.h"
+
+#include <stddef.h>
+
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/url_pattern_set.h"
+#include "extensions/renderer/api/automation/automation_api_helper.h"
+#include "extensions/renderer/dispatcher.h"
+#include "third_party/WebKit/public/platform/WebURLRequest.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+namespace extensions {
+
+ExtensionHelper::ExtensionHelper(content::RenderView* render_view,
+ Dispatcher* dispatcher)
+ : content::RenderViewObserver(render_view),
+ dispatcher_(dispatcher) {
+ // Lifecycle managed by RenderViewObserver.
+ new AutomationApiHelper(render_view);
+}
+
+ExtensionHelper::~ExtensionHelper() {
+}
+
+bool ExtensionHelper::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ExtensionHelper, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetFrameName, OnSetFrameName)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_AppWindowClosed,
+ OnAppWindowClosed)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ExtensionHelper::DraggableRegionsChanged(blink::WebFrame* frame) {
+ blink::WebVector<blink::WebDraggableRegion> webregions =
+ frame->document().draggableRegions();
+ std::vector<DraggableRegion> regions;
+ for (size_t i = 0; i < webregions.size(); ++i) {
+ DraggableRegion region;
+ render_view()->ConvertViewportToWindowViaWidget(&webregions[i].bounds);
+ region.bounds = webregions[i].bounds;
+ region.draggable = webregions[i].draggable;
+ regions.push_back(region);
+ }
+ Send(new ExtensionHostMsg_UpdateDraggableRegions(routing_id(), regions));
+}
+
+void ExtensionHelper::OnSetFrameName(const std::string& name) {
+ blink::WebView* web_view = render_view()->GetWebView();
+ if (web_view)
+ web_view->mainFrame()->setName(blink::WebString::fromUTF8(name));
+}
+
+void ExtensionHelper::OnAppWindowClosed() {
+ v8::HandleScope scope(v8::Isolate::GetCurrent());
+ v8::Local<v8::Context> v8_context =
+ render_view()->GetWebView()->mainFrame()->mainWorldScriptContext();
+ ScriptContext* script_context =
+ dispatcher_->script_context_set().GetByV8Context(v8_context);
+ if (!script_context)
+ return;
+ script_context->module_system()->CallModuleMethod("app.window",
+ "onAppWindowClosed");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extension_helper.h b/chromium/extensions/renderer/extension_helper.h
new file mode 100644
index 00000000000..a25a0a3f4ca
--- /dev/null
+++ b/chromium/extensions/renderer/extension_helper.h
@@ -0,0 +1,37 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSION_HELPER_H_
+#define EXTENSIONS_RENDERER_EXTENSION_HELPER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "content/public/renderer/render_view_observer.h"
+
+namespace extensions {
+class Dispatcher;
+
+// RenderView-level plumbing for extension features.
+class ExtensionHelper : public content::RenderViewObserver {
+ public:
+ ExtensionHelper(content::RenderView* render_view, Dispatcher* dispatcher);
+ ~ExtensionHelper() override;
+
+ private:
+ // RenderViewObserver implementation.
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void DraggableRegionsChanged(blink::WebFrame* frame) override;
+
+ void OnAppWindowClosed();
+ void OnSetFrameName(const std::string& name);
+
+ Dispatcher* dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionHelper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EXTENSION_HELPER_H_
diff --git a/chromium/extensions/renderer/extension_injection_host.cc b/chromium/extensions/renderer/extension_injection_host.cc
new file mode 100644
index 00000000000..6e5b0422871
--- /dev/null
+++ b/chromium/extensions/renderer/extension_injection_host.cc
@@ -0,0 +1,88 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/extension_injection_host.h"
+
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/manifest_handlers/csp_info.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "third_party/WebKit/public/platform/WebSecurityOrigin.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+namespace extensions {
+
+ExtensionInjectionHost::ExtensionInjectionHost(
+ const Extension* extension)
+ : InjectionHost(HostID(HostID::EXTENSIONS, extension->id())),
+ extension_(extension) {
+}
+
+ExtensionInjectionHost::~ExtensionInjectionHost() {
+}
+
+// static
+scoped_ptr<const InjectionHost> ExtensionInjectionHost::Create(
+ const std::string& extension_id) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (!extension)
+ return scoped_ptr<const ExtensionInjectionHost>();
+ return scoped_ptr<const ExtensionInjectionHost>(
+ new ExtensionInjectionHost(extension));
+}
+
+std::string ExtensionInjectionHost::GetContentSecurityPolicy() const {
+ return CSPInfo::GetContentSecurityPolicy(extension_);
+}
+
+const GURL& ExtensionInjectionHost::url() const {
+ return extension_->url();
+}
+
+const std::string& ExtensionInjectionHost::name() const {
+ return extension_->name();
+}
+
+PermissionsData::AccessType ExtensionInjectionHost::CanExecuteOnFrame(
+ const GURL& document_url,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ bool is_declarative) const {
+ blink::WebSecurityOrigin top_frame_security_origin =
+ render_frame->GetWebFrame()->top()->getSecurityOrigin();
+ // Only whitelisted extensions may run scripts on another extension's page.
+ if (top_frame_security_origin.protocol().utf8() == kExtensionScheme &&
+ top_frame_security_origin.host().utf8() != extension_->id() &&
+ !PermissionsData::CanExecuteScriptEverywhere(extension_))
+ return PermissionsData::ACCESS_DENIED;
+
+ // Declarative user scripts use "page access" (from "permissions" section in
+ // manifest) whereas non-declarative user scripts use custom
+ // "content script access" logic.
+ PermissionsData::AccessType access = PermissionsData::ACCESS_ALLOWED;
+ if (is_declarative) {
+ access = extension_->permissions_data()->GetPageAccess(
+ extension_,
+ document_url,
+ tab_id,
+ nullptr /* ignore error */);
+ } else {
+ access = extension_->permissions_data()->GetContentScriptAccess(
+ extension_,
+ document_url,
+ tab_id,
+ nullptr /* ignore error */);
+ }
+ if (access == PermissionsData::ACCESS_WITHHELD &&
+ (tab_id == -1 || render_frame->GetWebFrame()->parent())) {
+ // Note: we don't consider ACCESS_WITHHELD for child frames or for frames
+ // outside of tabs because there is nowhere to surface a request.
+ // TODO(devlin): We should ask for permission somehow. crbug.com/491402.
+ access = PermissionsData::ACCESS_DENIED;
+ }
+ return access;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extension_injection_host.h b/chromium/extensions/renderer/extension_injection_host.h
new file mode 100644
index 00000000000..a3c9d320dfd
--- /dev/null
+++ b/chromium/extensions/renderer/extension_injection_host.h
@@ -0,0 +1,45 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSION_INJECTION_HOST_H_
+#define EXTENSIONS_RENDERER_EXTENSION_INJECTION_HOST_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/common/extension.h"
+#include "extensions/renderer/injection_host.h"
+
+namespace extensions {
+
+// A wrapper class that holds an extension and implements the InjectionHost
+// interface.
+class ExtensionInjectionHost : public InjectionHost {
+ public:
+ ExtensionInjectionHost(const Extension* extension);
+ ~ExtensionInjectionHost() override;
+
+ // Create an ExtensionInjectionHost object. If the extension is gone, returns
+ // a null scoped ptr.
+ static scoped_ptr<const InjectionHost> Create(
+ const std::string& extension_id);
+
+ private:
+ // InjectionHost:
+ std::string GetContentSecurityPolicy() const override;
+ const GURL& url() const override;
+ const std::string& name() const override;
+ PermissionsData::AccessType CanExecuteOnFrame(
+ const GURL& document_url,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ bool is_declarative) const override;
+
+ const Extension* extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionInjectionHost);
+};
+
+} // namespace extesions
+
+#endif // EXTENSIONS_RENDERER_EXTENSION_INJECTION_HOST_H_
diff --git a/chromium/extensions/renderer/extensions_render_frame_observer.cc b/chromium/extensions/renderer/extensions_render_frame_observer.cc
new file mode 100644
index 00000000000..3f66f46256a
--- /dev/null
+++ b/chromium/extensions/renderer/extensions_render_frame_observer.cc
@@ -0,0 +1,100 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/extensions_render_frame_observer.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/stack_frame.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+namespace extensions {
+
+namespace {
+
+// The delimiter for a stack trace provided by WebKit.
+const char kStackFrameDelimiter[] = "\n at ";
+
+// Get a stack trace from a WebKit console message.
+// There are three possible scenarios:
+// 1. WebKit gives us a stack trace in |stack_trace|.
+// 2. The stack trace is embedded in the error |message| by an internal
+// script. This will be more useful than |stack_trace|, since |stack_trace|
+// will include the internal bindings trace, instead of a developer's code.
+// 3. No stack trace is included. In this case, we should mock one up from
+// the given line number and source.
+// |message| will be populated with the error message only (i.e., will not
+// include any stack trace).
+StackTrace GetStackTraceFromMessage(base::string16* message,
+ const base::string16& source,
+ const base::string16& stack_trace,
+ int32_t line_number) {
+ StackTrace result;
+ std::vector<base::string16> pieces;
+ size_t index = 0;
+
+ if (message->find(base::UTF8ToUTF16(kStackFrameDelimiter)) !=
+ base::string16::npos) {
+ base::SplitStringUsingSubstr(*message,
+ base::UTF8ToUTF16(kStackFrameDelimiter),
+ &pieces);
+ *message = pieces[0];
+ index = 1;
+ } else if (!stack_trace.empty()) {
+ base::SplitStringUsingSubstr(stack_trace,
+ base::UTF8ToUTF16(kStackFrameDelimiter),
+ &pieces);
+ }
+
+ // If we got a stack trace, parse each frame from the text.
+ if (index < pieces.size()) {
+ for (; index < pieces.size(); ++index) {
+ scoped_ptr<StackFrame> frame = StackFrame::CreateFromText(pieces[index]);
+ if (frame.get())
+ result.push_back(*frame);
+ }
+ }
+
+ if (result.empty()) { // If we don't have a stack trace, mock one up.
+ result.push_back(
+ StackFrame(line_number,
+ 1u, // column number
+ source,
+ base::string16() /* no function name */ ));
+ }
+
+ return result;
+}
+
+} // namespace
+
+ExtensionsRenderFrameObserver::ExtensionsRenderFrameObserver(
+ content::RenderFrame* render_frame)
+ : content::RenderFrameObserver(render_frame) {
+}
+
+ExtensionsRenderFrameObserver::~ExtensionsRenderFrameObserver() {
+}
+
+void ExtensionsRenderFrameObserver::DetailedConsoleMessageAdded(
+ const base::string16& message,
+ const base::string16& source,
+ const base::string16& stack_trace_string,
+ uint32_t line_number,
+ int32_t severity_level) {
+ base::string16 trimmed_message = message;
+ StackTrace stack_trace = GetStackTraceFromMessage(
+ &trimmed_message,
+ source,
+ stack_trace_string,
+ line_number);
+ Send(new ExtensionHostMsg_DetailedConsoleMessageAdded(
+ routing_id(), trimmed_message, source, stack_trace, severity_level));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extensions_render_frame_observer.h b/chromium/extensions/renderer/extensions_render_frame_observer.h
new file mode 100644
index 00000000000..55521b2b7b5
--- /dev/null
+++ b/chromium/extensions/renderer/extensions_render_frame_observer.h
@@ -0,0 +1,38 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSIONS_RENDER_FRAME_OBSERVER_H_
+#define EXTENSIONS_RENDERER_EXTENSIONS_RENDER_FRAME_OBSERVER_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "content/public/renderer/render_frame_observer.h"
+
+namespace extensions {
+
+// This class holds the extensions specific parts of RenderFrame, and has the
+// same lifetime.
+class ExtensionsRenderFrameObserver
+ : public content::RenderFrameObserver {
+ public:
+ explicit ExtensionsRenderFrameObserver(
+ content::RenderFrame* render_frame);
+ ~ExtensionsRenderFrameObserver() override;
+
+ private:
+ // RenderFrameObserver implementation.
+ void DetailedConsoleMessageAdded(const base::string16& message,
+ const base::string16& source,
+ const base::string16& stack_trace,
+ uint32_t line_number,
+ int32_t severity_level) override;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionsRenderFrameObserver);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EXTENSIONS_RENDER_FRAME_OBSERVER_H_
+
diff --git a/chromium/extensions/renderer/extensions_renderer_client.cc b/chromium/extensions/renderer/extensions_renderer_client.cc
new file mode 100644
index 00000000000..26c46ed70ed
--- /dev/null
+++ b/chromium/extensions/renderer/extensions_renderer_client.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/extensions_renderer_client.h"
+
+#include "base/logging.h"
+
+namespace extensions {
+
+namespace {
+
+ExtensionsRendererClient* g_client = NULL;
+
+} // namespace
+
+ExtensionsRendererClient* ExtensionsRendererClient::Get() {
+ CHECK(g_client);
+ return g_client;
+}
+
+void ExtensionsRendererClient::Set(ExtensionsRendererClient* client) {
+ g_client = client;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extensions_renderer_client.h b/chromium/extensions/renderer/extensions_renderer_client.h
new file mode 100644
index 00000000000..ae4a31ba689
--- /dev/null
+++ b/chromium/extensions/renderer/extensions_renderer_client.h
@@ -0,0 +1,39 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSIONS_RENDERER_CLIENT_H_
+#define EXTENSIONS_RENDERER_EXTENSIONS_RENDERER_CLIENT_H_
+
+class ResourceBundleSourceMap;
+
+namespace extensions {
+
+// Interface to allow the extensions module to make render-process-specific
+// queries of the embedder. Should be Set() once in the render process.
+//
+// NOTE: Methods that do not require knowledge of renderer concepts should be
+// added in ExtensionsClient (extensions/common/extensions_client.h) even if
+// they are only used in the renderer process.
+class ExtensionsRendererClient {
+ public:
+ virtual ~ExtensionsRendererClient() {}
+
+ // Returns true if the current render process was launched incognito.
+ virtual bool IsIncognitoProcess() const = 0;
+
+ // Returns the lowest isolated world ID available to extensions.
+ // Must be greater than 0. See blink::WebFrame::executeScriptInIsolatedWorld
+ // (third_party/WebKit/public/web/WebFrame.h) for additional context.
+ virtual int GetLowestIsolatedWorldId() const = 0;
+
+ // Returns the single instance of |this|.
+ static ExtensionsRendererClient* Get();
+
+ // Initialize the single instance.
+ static void Set(ExtensionsRendererClient* client);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EXTENSIONS_RENDERER_CLIENT_H_
diff --git a/chromium/extensions/renderer/file_system_natives.cc b/chromium/extensions/renderer/file_system_natives.cc
new file mode 100644
index 00000000000..2b7006299a2
--- /dev/null
+++ b/chromium/extensions/renderer/file_system_natives.cc
@@ -0,0 +1,124 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/file_system_natives.h"
+
+#include <string>
+
+#include "extensions/common/constants.h"
+#include "extensions/renderer/script_context.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/web/WebDOMFileSystem.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "url/origin.h"
+
+namespace extensions {
+
+FileSystemNatives::FileSystemNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetFileEntry",
+ base::Bind(&FileSystemNatives::GetFileEntry, base::Unretained(this)));
+ RouteFunction("GetIsolatedFileSystem",
+ base::Bind(&FileSystemNatives::GetIsolatedFileSystem,
+ base::Unretained(this)));
+ RouteFunction("CrackIsolatedFileSystemName",
+ base::Bind(&FileSystemNatives::CrackIsolatedFileSystemName,
+ base::Unretained(this)));
+}
+
+void FileSystemNatives::GetIsolatedFileSystem(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 1 || args.Length() == 2);
+ CHECK(args[0]->IsString());
+ std::string file_system_id(*v8::String::Utf8Value(args[0]));
+ blink::WebLocalFrame* webframe =
+ blink::WebLocalFrame::frameForContext(context()->v8_context());
+ DCHECK(webframe);
+
+ GURL context_url =
+ extensions::ScriptContext::GetDataSourceURLForFrame(webframe);
+ CHECK(context_url.SchemeIs(extensions::kExtensionScheme));
+
+ const GURL origin(url::Origin(context_url).Serialize());
+ std::string name(storage::GetIsolatedFileSystemName(origin, file_system_id));
+
+ // The optional second argument is the subfolder within the isolated file
+ // system at which to root the DOMFileSystem we're returning to the caller.
+ std::string optional_root_name;
+ if (args.Length() == 2) {
+ CHECK(args[1]->IsString());
+ optional_root_name = *v8::String::Utf8Value(args[1]);
+ }
+
+ GURL root_url(storage::GetIsolatedFileSystemRootURIString(
+ origin, file_system_id, optional_root_name));
+
+ args.GetReturnValue().Set(
+ blink::WebDOMFileSystem::create(webframe,
+ blink::WebFileSystemTypeIsolated,
+ blink::WebString::fromUTF8(name),
+ root_url)
+ .toV8Value(context()->v8_context()->Global(), args.GetIsolate()));
+}
+
+void FileSystemNatives::GetFileEntry(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(5, args.Length());
+ CHECK(args[0]->IsString());
+ std::string type_string = *v8::String::Utf8Value(args[0]);
+ blink::WebFileSystemType type;
+ bool is_valid_type = storage::GetFileSystemPublicType(type_string, &type);
+ DCHECK(is_valid_type);
+ if (is_valid_type == false) {
+ return;
+ }
+
+ CHECK(args[1]->IsString());
+ CHECK(args[2]->IsString());
+ CHECK(args[3]->IsString());
+ std::string file_system_name(*v8::String::Utf8Value(args[1]));
+ GURL file_system_root_url(*v8::String::Utf8Value(args[2]));
+ std::string file_path_string(*v8::String::Utf8Value(args[3]));
+ base::FilePath file_path = base::FilePath::FromUTF8Unsafe(file_path_string);
+ DCHECK(storage::VirtualPath::IsAbsolute(file_path.value()));
+
+ CHECK(args[4]->IsBoolean());
+ blink::WebDOMFileSystem::EntryType entry_type =
+ args[4]->BooleanValue() ? blink::WebDOMFileSystem::EntryTypeDirectory
+ : blink::WebDOMFileSystem::EntryTypeFile;
+
+ blink::WebLocalFrame* webframe =
+ blink::WebLocalFrame::frameForContext(context()->v8_context());
+ DCHECK(webframe);
+ args.GetReturnValue().Set(
+ blink::WebDOMFileSystem::create(
+ webframe,
+ type,
+ blink::WebString::fromUTF8(file_system_name),
+ file_system_root_url)
+ .createV8Entry(blink::WebString::fromUTF8(file_path_string),
+ entry_type,
+ context()->v8_context()->Global(),
+ args.GetIsolate()));
+}
+
+void FileSystemNatives::CrackIsolatedFileSystemName(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ DCHECK_EQ(args.Length(), 1);
+ DCHECK(args[0]->IsString());
+ std::string filesystem_name = *v8::String::Utf8Value(args[0]);
+ std::string filesystem_id;
+ if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id))
+ return;
+
+ args.GetReturnValue().Set(v8::String::NewFromUtf8(args.GetIsolate(),
+ filesystem_id.c_str(),
+ v8::String::kNormalString,
+ filesystem_id.size()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/file_system_natives.h b/chromium/extensions/renderer/file_system_natives.h
new file mode 100644
index 00000000000..102f2200644
--- /dev/null
+++ b/chromium/extensions/renderer/file_system_natives.h
@@ -0,0 +1,31 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_FILE_SYSTEM_NATIVES_H_
+#define EXTENSIONS_RENDERER_FILE_SYSTEM_NATIVES_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Custom bindings for the nativeFileSystem API.
+class FileSystemNatives : public ObjectBackedNativeHandler {
+ public:
+ explicit FileSystemNatives(ScriptContext* context);
+
+ private:
+ void GetFileEntry(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetIsolatedFileSystem(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void CrackIsolatedFileSystemName(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemNatives);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_FILE_SYSTEM_NATIVES_H_
diff --git a/chromium/extensions/renderer/gc_callback.cc b/chromium/extensions/renderer/gc_callback.cc
new file mode 100644
index 00000000000..46b7f8acdf1
--- /dev/null
+++ b/chromium/extensions/renderer/gc_callback.cc
@@ -0,0 +1,58 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/gc_callback.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+GCCallback::GCCallback(ScriptContext* context,
+ const v8::Local<v8::Object>& object,
+ const v8::Local<v8::Function>& callback,
+ const base::Closure& fallback)
+ : context_(context),
+ object_(context->isolate(), object),
+ callback_(context->isolate(), callback),
+ fallback_(fallback),
+ weak_ptr_factory_(this) {
+ object_.SetWeak(this, OnObjectGC, v8::WeakCallbackType::kParameter);
+ context->AddInvalidationObserver(base::Bind(&GCCallback::OnContextInvalidated,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+GCCallback::~GCCallback() {}
+
+// static
+void GCCallback::OnObjectGC(const v8::WeakCallbackInfo<GCCallback>& data) {
+ // Usually FirstWeakCallback should do nothing other than reset |object_|
+ // and then set a second weak callback to run later. We can sidestep that,
+ // because posting a task to the current message loop is all but free - but
+ // DO NOT add any more work to this method. The only acceptable place to add
+ // code is RunCallback.
+ GCCallback* self = data.GetParameter();
+ self->object_.Reset();
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&GCCallback::RunCallback,
+ self->weak_ptr_factory_.GetWeakPtr()));
+}
+
+void GCCallback::RunCallback() {
+ fallback_.Reset();
+ v8::Isolate* isolate = context_->isolate();
+ v8::HandleScope handle_scope(isolate);
+ context_->CallFunction(v8::Local<v8::Function>::New(isolate, callback_));
+ delete this;
+}
+
+void GCCallback::OnContextInvalidated() {
+ if (!fallback_.is_null()) {
+ fallback_.Run();
+ delete this;
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/gc_callback.h b/chromium/extensions/renderer/gc_callback.h
new file mode 100644
index 00000000000..96dc2441aed
--- /dev/null
+++ b/chromium/extensions/renderer/gc_callback.h
@@ -0,0 +1,56 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_GC_CALLBACK_H_
+#define EXTENSIONS_RENDERER_GC_CALLBACK_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+class ScriptContext;
+
+// Runs |callback| when v8 garbage collects |object|, or |fallback| if
+// |context| is invalidated first. Exactly one of |callback| or |fallback| will
+// be called, after which it deletes itself.
+class GCCallback {
+ public:
+ GCCallback(ScriptContext* context,
+ const v8::Local<v8::Object>& object,
+ const v8::Local<v8::Function>& callback,
+ const base::Closure& fallback);
+ ~GCCallback();
+
+ private:
+ static void OnObjectGC(const v8::WeakCallbackInfo<GCCallback>& data);
+ void RunCallback();
+ void OnContextInvalidated();
+
+ // The context which owns |object_|.
+ ScriptContext* context_;
+
+ // The object this GCCallback is bound to.
+ v8::Global<v8::Object> object_;
+
+ // The function to run when |object_| is garbage collected.
+ v8::Global<v8::Function> callback_;
+
+ // The function to run if |context_| is invalidated before we have a chance
+ // to execute |callback_|.
+ base::Closure fallback_;
+
+ base::WeakPtrFactory<GCCallback> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GCCallback);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_GC_CALLBACK_H_
diff --git a/chromium/extensions/renderer/gc_callback_unittest.cc b/chromium/extensions/renderer/gc_callback_unittest.cc
new file mode 100644
index 00000000000..f1b70ba3e24
--- /dev/null
+++ b/chromium/extensions/renderer/gc_callback_unittest.cc
@@ -0,0 +1,161 @@
+// Copyright 2015 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.
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/gc_callback.h"
+#include "extensions/renderer/scoped_web_frame.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "gin/function_template.h"
+#include "gin/public/context_holder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+namespace {
+
+void SetToTrue(bool* value) {
+ if (*value)
+ ADD_FAILURE() << "Value is already true";
+ *value = true;
+}
+
+class GCCallbackTest : public testing::Test {
+ public:
+ GCCallbackTest() : script_context_set_(&active_extensions_) {}
+
+ protected:
+ base::MessageLoop& message_loop() { return message_loop_; }
+
+ ScriptContextSet& script_context_set() { return script_context_set_; }
+
+ v8::Local<v8::Context> v8_context() {
+ return v8::Local<v8::Context>::New(v8::Isolate::GetCurrent(), v8_context_);
+ }
+
+ ScriptContext* RegisterScriptContext() {
+ // No extension group or world ID.
+ return script_context_set_.Register(
+ web_frame_.frame(),
+ v8::Local<v8::Context>::New(v8::Isolate::GetCurrent(), v8_context_), 0,
+ 0);
+ }
+
+ void RequestGarbageCollection() {
+ v8::Isolate::GetCurrent()->RequestGarbageCollectionForTesting(
+ v8::Isolate::kFullGarbageCollection);
+ }
+
+ private:
+ void SetUp() override {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> local_v8_context = v8::Context::New(isolate);
+ v8_context_.Reset(isolate, local_v8_context);
+ // ScriptContexts rely on gin.
+ gin_context_holder_.reset(new gin::ContextHolder(isolate));
+ gin_context_holder_->SetContext(local_v8_context);
+ }
+
+ void TearDown() override {
+ gin_context_holder_.reset();
+ v8_context_.Reset();
+ RequestGarbageCollection();
+ }
+
+ base::MessageLoop message_loop_;
+ ScopedWebFrame web_frame_; // (this will construct the v8::Isolate)
+ ExtensionIdSet active_extensions_;
+ ScriptContextSet script_context_set_;
+ v8::Global<v8::Context> v8_context_;
+ scoped_ptr<gin::ContextHolder> gin_context_holder_;
+
+ DISALLOW_COPY_AND_ASSIGN(GCCallbackTest);
+};
+
+TEST_F(GCCallbackTest, GCBeforeContextInvalidated) {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(v8_context());
+
+ ScriptContext* script_context = RegisterScriptContext();
+
+ bool callback_invoked = false;
+ bool fallback_invoked = false;
+
+ {
+ // Nest another HandleScope so that |object| and |unreachable_function|'s
+ // handles will be garbage collected.
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Object> object = v8::Object::New(isolate);
+ v8::Local<v8::FunctionTemplate> unreachable_function =
+ gin::CreateFunctionTemplate(isolate,
+ base::Bind(SetToTrue, &callback_invoked));
+ // The GCCallback will delete itself, or memory tests will complain.
+ new GCCallback(script_context, object, unreachable_function->GetFunction(),
+ base::Bind(SetToTrue, &fallback_invoked));
+ }
+
+ // Trigger a GC. Only the callback should be invoked.
+ RequestGarbageCollection();
+ message_loop().RunUntilIdle();
+
+ EXPECT_TRUE(callback_invoked);
+ EXPECT_FALSE(fallback_invoked);
+
+ // Invalidate the context. The fallback should not be invoked because the
+ // callback was already invoked.
+ script_context_set().Remove(script_context);
+ message_loop().RunUntilIdle();
+
+ EXPECT_FALSE(fallback_invoked);
+}
+
+TEST_F(GCCallbackTest, ContextInvalidatedBeforeGC) {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(v8_context());
+
+ ScriptContext* script_context = RegisterScriptContext();
+
+ bool callback_invoked = false;
+ bool fallback_invoked = false;
+
+ {
+ // Nest another HandleScope so that |object| and |unreachable_function|'s
+ // handles will be garbage collected.
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Object> object = v8::Object::New(isolate);
+ v8::Local<v8::FunctionTemplate> unreachable_function =
+ gin::CreateFunctionTemplate(isolate,
+ base::Bind(SetToTrue, &callback_invoked));
+ // The GCCallback will delete itself, or memory tests will complain.
+ new GCCallback(script_context, object, unreachable_function->GetFunction(),
+ base::Bind(SetToTrue, &fallback_invoked));
+ }
+
+ // Invalidate the context. Only the fallback should be invoked.
+ script_context_set().Remove(script_context);
+ message_loop().RunUntilIdle();
+
+ EXPECT_FALSE(callback_invoked);
+ EXPECT_TRUE(fallback_invoked);
+
+ // Trigger a GC. The callback should not be invoked because the fallback was
+ // already invoked.
+ RequestGarbageCollection();
+ message_loop().RunUntilIdle();
+
+ EXPECT_FALSE(callback_invoked);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/OWNERS b/chromium/extensions/renderer/guest_view/OWNERS
new file mode 100644
index 00000000000..51bd1721001
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/OWNERS
@@ -0,0 +1,5 @@
+fsamuel@chromium.org
+lazyboy@chromium.org
+lfg@chromium.org
+hanxi@chromium.org
+wjmaclean@chromium.org
diff --git a/chromium/extensions/renderer/guest_view/extensions_guest_view_container.cc b/chromium/extensions/renderer/guest_view/extensions_guest_view_container.cc
new file mode 100644
index 00000000000..ffa3580e3fe
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/extensions_guest_view_container.cc
@@ -0,0 +1,64 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/guest_view/extensions_guest_view_container.h"
+
+#include "content/public/renderer/render_frame.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace extensions {
+
+ExtensionsGuestViewContainer::ExtensionsGuestViewContainer(
+ content::RenderFrame* render_frame)
+ : GuestViewContainer(render_frame),
+ element_resize_isolate_(nullptr),
+ weak_ptr_factory_(this) {
+}
+
+ExtensionsGuestViewContainer::~ExtensionsGuestViewContainer() {
+}
+
+void ExtensionsGuestViewContainer::OnDestroy(bool embedder_frame_destroyed) {
+}
+
+void ExtensionsGuestViewContainer::RegisterElementResizeCallback(
+ v8::Local<v8::Function> callback,
+ v8::Isolate* isolate) {
+ element_resize_callback_.Reset(isolate, callback);
+ element_resize_isolate_ = isolate;
+}
+
+void ExtensionsGuestViewContainer::DidResizeElement(const gfx::Size& new_size) {
+ // Call the element resize callback, if one is registered.
+ if (element_resize_callback_.IsEmpty())
+ return;
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ExtensionsGuestViewContainer::CallElementResizeCallback,
+ weak_ptr_factory_.GetWeakPtr(), new_size));
+}
+
+void ExtensionsGuestViewContainer::CallElementResizeCallback(
+ const gfx::Size& new_size) {
+ v8::HandleScope handle_scope(element_resize_isolate_);
+ v8::Local<v8::Function> callback = v8::Local<v8::Function>::New(
+ element_resize_isolate_, element_resize_callback_);
+ v8::Local<v8::Context> context = callback->CreationContext();
+ if (context.IsEmpty())
+ return;
+
+ const int argc = 2;
+ v8::Local<v8::Value> argv[argc] = {
+ v8::Integer::New(element_resize_isolate_, new_size.width()),
+ v8::Integer::New(element_resize_isolate_, new_size.height())};
+
+ v8::Context::Scope context_scope(context);
+ v8::MicrotasksScope microtasks(
+ element_resize_isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);
+
+ callback->Call(context->Global(), argc, argv);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/extensions_guest_view_container.h b/chromium/extensions/renderer/guest_view/extensions_guest_view_container.h
new file mode 100644
index 00000000000..bc95b187f80
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/extensions_guest_view_container.h
@@ -0,0 +1,50 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_CONTAINER_H_
+#define EXTENSIONS_RENDERER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_CONTAINER_H_
+
+#include <queue>
+
+#include "base/macros.h"
+#include "components/guest_view/renderer/guest_view_container.h"
+#include "v8/include/v8.h"
+
+namespace gfx {
+class Size;
+}
+
+namespace extensions {
+
+class ExtensionsGuestViewContainer : public guest_view::GuestViewContainer {
+ public:
+ explicit ExtensionsGuestViewContainer(content::RenderFrame* render_frame);
+
+ void RegisterElementResizeCallback(v8::Local<v8::Function> callback,
+ v8::Isolate* isolate);
+
+ // BrowserPluginDelegate implementation.
+ void DidResizeElement(const gfx::Size& new_size) override;
+
+ protected:
+ ~ExtensionsGuestViewContainer() override;
+
+ private:
+ void CallElementResizeCallback(const gfx::Size& new_size);
+
+ // GuestViewContainer implementation.
+ void OnDestroy(bool embedder_frame_destroyed) override;
+
+ v8::Global<v8::Function> element_resize_callback_;
+ v8::Isolate* element_resize_isolate_;
+
+ // Weak pointer factory used for calling the element resize callback.
+ base::WeakPtrFactory<ExtensionsGuestViewContainer> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionsGuestViewContainer);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_CONTAINER_H_
diff --git a/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.cc b/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.cc
new file mode 100644
index 00000000000..16f1f4771e4
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.cc
@@ -0,0 +1,26 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h"
+
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace extensions {
+
+ExtensionsGuestViewContainerDispatcher::
+ ExtensionsGuestViewContainerDispatcher() {
+}
+
+ExtensionsGuestViewContainerDispatcher::
+ ~ExtensionsGuestViewContainerDispatcher() {
+}
+
+bool ExtensionsGuestViewContainerDispatcher::HandlesMessage(
+ const IPC::Message& message) {
+ return GuestViewContainerDispatcher::HandlesMessage(message) ||
+ (IPC_MESSAGE_CLASS(message) == ExtensionsGuestViewMsgStart);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h b/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h
new file mode 100644
index 00000000000..b04ee04f1e0
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h
@@ -0,0 +1,23 @@
+// Copyright 2015 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.
+
+#include "base/macros.h"
+#include "components/guest_view/renderer/guest_view_container_dispatcher.h"
+
+namespace extensions {
+
+class ExtensionsGuestViewContainerDispatcher
+ : public guest_view::GuestViewContainerDispatcher {
+ public:
+ ExtensionsGuestViewContainerDispatcher();
+ ~ExtensionsGuestViewContainerDispatcher() override;
+
+ private:
+ // guest_view::GuestViewContainerDispatcher implementation.
+ bool HandlesMessage(const IPC::Message& message) override;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionsGuestViewContainerDispatcher);
+};
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc b/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc
new file mode 100644
index 00000000000..f54c87891c7
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc
@@ -0,0 +1,438 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/guest_view/guest_view_internal_custom_bindings.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "components/guest_view/common/guest_view_messages.h"
+#include "components/guest_view/renderer/guest_view_request.h"
+#include "components/guest_view/renderer/iframe_guest_view_container.h"
+#include "components/guest_view/renderer/iframe_guest_view_request.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/guest_view/extensions_guest_view_messages.h"
+#include "extensions/renderer/guest_view/extensions_guest_view_container.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebRemoteFrame.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "v8/include/v8.h"
+
+using content::V8ValueConverter;
+
+namespace {
+
+// A map from view instance ID to view object (stored via weak V8 reference).
+// Views are registered into this map via
+// GuestViewInternalCustomBindings::RegisterView(), and accessed via
+// GuestViewInternalCustomBindings::GetViewFromID().
+using ViewMap = std::map<int, v8::Global<v8::Object>*>;
+static base::LazyInstance<ViewMap> weak_view_map = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace extensions {
+
+namespace {
+
+content::RenderFrame* GetRenderFrame(v8::Handle<v8::Value> value) {
+ v8::Local<v8::Context> context =
+ v8::Local<v8::Object>::Cast(value)->CreationContext();
+ if (context.IsEmpty())
+ return nullptr;
+ blink::WebLocalFrame* frame = blink::WebLocalFrame::frameForContext(context);
+ if (!frame)
+ return nullptr;
+ return content::RenderFrame::FromWebFrame(frame);
+}
+
+} // namespace
+
+GuestViewInternalCustomBindings::GuestViewInternalCustomBindings(
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("AttachGuest",
+ base::Bind(&GuestViewInternalCustomBindings::AttachGuest,
+ base::Unretained(this)));
+ RouteFunction("DetachGuest",
+ base::Bind(&GuestViewInternalCustomBindings::DetachGuest,
+ base::Unretained(this)));
+ RouteFunction("AttachIframeGuest",
+ base::Bind(&GuestViewInternalCustomBindings::AttachIframeGuest,
+ base::Unretained(this)));
+ RouteFunction("DestroyContainer",
+ base::Bind(&GuestViewInternalCustomBindings::DestroyContainer,
+ base::Unretained(this)));
+ RouteFunction("GetContentWindow",
+ base::Bind(&GuestViewInternalCustomBindings::GetContentWindow,
+ base::Unretained(this)));
+ RouteFunction("GetViewFromID",
+ base::Bind(&GuestViewInternalCustomBindings::GetViewFromID,
+ base::Unretained(this)));
+ RouteFunction(
+ "RegisterDestructionCallback",
+ base::Bind(&GuestViewInternalCustomBindings::RegisterDestructionCallback,
+ base::Unretained(this)));
+ RouteFunction(
+ "RegisterElementResizeCallback",
+ base::Bind(
+ &GuestViewInternalCustomBindings::RegisterElementResizeCallback,
+ base::Unretained(this)));
+ RouteFunction("RegisterView",
+ base::Bind(&GuestViewInternalCustomBindings::RegisterView,
+ base::Unretained(this)));
+ RouteFunction(
+ "RunWithGesture",
+ base::Bind(&GuestViewInternalCustomBindings::RunWithGesture,
+ base::Unretained(this)));
+}
+
+GuestViewInternalCustomBindings::~GuestViewInternalCustomBindings() {}
+
+// static
+void GuestViewInternalCustomBindings::ResetMapEntry(
+ const v8::WeakCallbackInfo<int>& data) {
+ int* param = data.GetParameter();
+ int view_instance_id = *param;
+ delete param;
+ ViewMap& view_map = weak_view_map.Get();
+ auto entry = view_map.find(view_instance_id);
+ if (entry == view_map.end())
+ return;
+
+ // V8 says we need to explicitly reset weak handles from their callbacks.
+ // It is not implicit as one might expect.
+ entry->second->Reset();
+ delete entry->second;
+ view_map.erase(entry);
+
+ // Let the GuestViewManager know that a GuestView has been garbage collected.
+ content::RenderThread::Get()->Send(
+ new GuestViewHostMsg_ViewGarbageCollected(view_instance_id));
+}
+
+void GuestViewInternalCustomBindings::AttachGuest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Allow for an optional callback parameter.
+ CHECK(args.Length() >= 3 && args.Length() <= 4);
+ // Element Instance ID.
+ CHECK(args[0]->IsInt32());
+ // Guest Instance ID.
+ CHECK(args[1]->IsInt32());
+ // Attach Parameters.
+ CHECK(args[2]->IsObject());
+ // Optional Callback Function.
+ CHECK(args.Length() < 4 || args[3]->IsFunction());
+
+ int element_instance_id = args[0]->Int32Value();
+ // An element instance ID uniquely identifies a GuestViewContainer.
+ auto guest_view_container =
+ guest_view::GuestViewContainer::FromID(element_instance_id);
+
+ // TODO(fsamuel): Should we be reporting an error if the element instance ID
+ // is invalid?
+ if (!guest_view_container)
+ return;
+
+ int guest_instance_id = args[1]->Int32Value();
+
+ scoped_ptr<base::DictionaryValue> params;
+ {
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ scoped_ptr<base::Value> params_as_value(
+ converter->FromV8Value(args[2], context()->v8_context()));
+ params = base::DictionaryValue::From(std::move(params_as_value));
+ CHECK(params);
+ }
+
+ // Add flag to |params| to indicate that the element size is specified in
+ // logical units.
+ params->SetBoolean(guest_view::kElementSizeIsLogical, true);
+
+ linked_ptr<guest_view::GuestViewRequest> request(
+ new guest_view::GuestViewAttachRequest(
+ guest_view_container, guest_instance_id, std::move(params),
+ args.Length() == 4 ? args[3].As<v8::Function>()
+ : v8::Local<v8::Function>(),
+ args.GetIsolate()));
+ guest_view_container->IssueRequest(request);
+
+ args.GetReturnValue().Set(v8::Boolean::New(context()->isolate(), true));
+}
+
+void GuestViewInternalCustomBindings::DetachGuest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Allow for an optional callback parameter.
+ CHECK(args.Length() >= 1 && args.Length() <= 2);
+ // Element Instance ID.
+ CHECK(args[0]->IsInt32());
+ // Optional Callback Function.
+ CHECK(args.Length() < 2 || args[1]->IsFunction());
+
+ int element_instance_id = args[0]->Int32Value();
+ // An element instance ID uniquely identifies a GuestViewContainer.
+ auto guest_view_container =
+ guest_view::GuestViewContainer::FromID(element_instance_id);
+
+ // TODO(fsamuel): Should we be reporting an error if the element instance ID
+ // is invalid?
+ if (!guest_view_container)
+ return;
+
+ linked_ptr<guest_view::GuestViewRequest> request(
+ new guest_view::GuestViewDetachRequest(
+ guest_view_container, args.Length() == 2 ? args[1].As<v8::Function>()
+ : v8::Local<v8::Function>(),
+ args.GetIsolate()));
+ guest_view_container->IssueRequest(request);
+
+ args.GetReturnValue().Set(v8::Boolean::New(context()->isolate(), true));
+}
+
+void GuestViewInternalCustomBindings::AttachIframeGuest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Allow for an optional callback parameter.
+ const int num_required_params = 4;
+ CHECK(args.Length() >= num_required_params &&
+ args.Length() <= (num_required_params + 1));
+ // Element Instance ID.
+ CHECK(args[0]->IsInt32());
+ // Guest Instance ID.
+ CHECK(args[1]->IsInt32());
+ // Attach Parameters.
+ CHECK(args[2]->IsObject());
+ // <iframe>.contentWindow.
+ CHECK(args[3]->IsObject());
+ // Optional Callback Function.
+ CHECK(args.Length() <= num_required_params ||
+ args[num_required_params]->IsFunction());
+
+ int element_instance_id = args[0]->Int32Value();
+ int guest_instance_id = args[1]->Int32Value();
+
+ scoped_ptr<base::DictionaryValue> params;
+ {
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ scoped_ptr<base::Value> params_as_value(
+ converter->FromV8Value(args[2], context()->v8_context()));
+ params = base::DictionaryValue::From(std::move(params_as_value));
+ CHECK(params);
+ }
+
+ // Add flag to |params| to indicate that the element size is specified in
+ // logical units.
+ params->SetBoolean(guest_view::kElementSizeIsLogical, true);
+
+ content::RenderFrame* render_frame = GetRenderFrame(args[3]);
+ blink::WebLocalFrame* frame = render_frame->GetWebFrame();
+
+ // Parent must exist.
+ blink::WebFrame* parent_frame = frame->parent();
+ DCHECK(parent_frame);
+ DCHECK(parent_frame->isWebLocalFrame());
+
+ content::RenderFrame* embedder_parent_frame =
+ content::RenderFrame::FromWebFrame(parent_frame);
+
+ // Create a GuestViewContainer if it does not exist.
+ // An element instance ID uniquely identifies an IframeGuestViewContainer
+ // within a RenderView.
+ auto* guest_view_container =
+ static_cast<guest_view::IframeGuestViewContainer*>(
+ guest_view::GuestViewContainer::FromID(element_instance_id));
+ // This is the first time we hear about the |element_instance_id|.
+ DCHECK(!guest_view_container);
+ // The <webview> element's GC takes ownership of |guest_view_container|.
+ guest_view_container =
+ new guest_view::IframeGuestViewContainer(embedder_parent_frame);
+ guest_view_container->SetElementInstanceID(element_instance_id);
+
+ linked_ptr<guest_view::GuestViewRequest> request(
+ new guest_view::GuestViewAttachIframeRequest(
+ guest_view_container, render_frame->GetRoutingID(), guest_instance_id,
+ std::move(params), args.Length() == (num_required_params + 1)
+ ? args[num_required_params].As<v8::Function>()
+ : v8::Local<v8::Function>(),
+ args.GetIsolate()));
+ guest_view_container->IssueRequest(request);
+
+ args.GetReturnValue().Set(v8::Boolean::New(context()->isolate(), true));
+}
+
+void GuestViewInternalCustomBindings::DestroyContainer(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().SetNull();
+
+ if (args.Length() != 1)
+ return;
+
+ // Element Instance ID.
+ if (!args[0]->IsInt32())
+ return;
+
+ int element_instance_id = args[0]->Int32Value();
+ auto* guest_view_container =
+ guest_view::GuestViewContainer::FromID(element_instance_id);
+ if (!guest_view_container)
+ return;
+
+ // Note: |guest_view_container| is deleted.
+ // GuestViewContainer::DidDestroyElement() currently also destroys
+ // a GuestViewContainer. That won't be necessary once GuestViewContainer
+ // always runs w/o plugin.
+ guest_view_container->Destroy(false /* embedder_frame_destroyed */);
+}
+
+void GuestViewInternalCustomBindings::GetContentWindow(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Default to returning null.
+ args.GetReturnValue().SetNull();
+
+ if (args.Length() != 1)
+ return;
+
+ // The routing ID for the RenderView.
+ if (!args[0]->IsInt32())
+ return;
+
+ int view_id = args[0]->Int32Value();
+ if (view_id == MSG_ROUTING_NONE)
+ return;
+
+ content::RenderView* view = content::RenderView::FromRoutingID(view_id);
+ if (!view)
+ return;
+
+ blink::WebFrame* frame = view->GetWebView()->mainFrame();
+ // TODO(lazyboy,nasko): The WebLocalFrame branch is not used when running
+ // on top of out-of-process iframes. Remove it once the code is converted.
+ v8::Local<v8::Value> window;
+ if (frame->isWebLocalFrame()) {
+ window = frame->mainWorldScriptContext()->Global();
+ } else {
+ window =
+ frame->toWebRemoteFrame()->deprecatedMainWorldScriptContext()->Global();
+ }
+ args.GetReturnValue().Set(window);
+}
+
+void GuestViewInternalCustomBindings::GetViewFromID(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Default to returning null.
+ args.GetReturnValue().SetNull();
+ // There is one argument.
+ CHECK(args.Length() == 1);
+ // The view ID.
+ CHECK(args[0]->IsInt32());
+ int view_id = args[0]->Int32Value();
+
+ ViewMap& view_map = weak_view_map.Get();
+ auto map_entry = view_map.find(view_id);
+ if (map_entry == view_map.end())
+ return;
+
+ auto return_object = v8::Handle<v8::Object>::New(args.GetIsolate(),
+ *map_entry->second);
+ args.GetReturnValue().Set(return_object);
+}
+
+void GuestViewInternalCustomBindings::RegisterDestructionCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // There are two parameters.
+ CHECK(args.Length() == 2);
+ // Element Instance ID.
+ CHECK(args[0]->IsInt32());
+ // Callback function.
+ CHECK(args[1]->IsFunction());
+
+ int element_instance_id = args[0]->Int32Value();
+ // An element instance ID uniquely identifies a GuestViewContainer within a
+ // RenderView.
+ auto* guest_view_container =
+ guest_view::GuestViewContainer::FromID(element_instance_id);
+ if (!guest_view_container)
+ return;
+
+ guest_view_container->RegisterDestructionCallback(args[1].As<v8::Function>(),
+ args.GetIsolate());
+
+ args.GetReturnValue().Set(v8::Boolean::New(context()->isolate(), true));
+}
+
+void GuestViewInternalCustomBindings::RegisterElementResizeCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // There are two parameters.
+ CHECK(args.Length() == 2);
+ // Element Instance ID.
+ CHECK(args[0]->IsInt32());
+ // Callback function.
+ CHECK(args[1]->IsFunction());
+
+ int element_instance_id = args[0]->Int32Value();
+ // An element instance ID uniquely identifies a ExtensionsGuestViewContainer
+ // within a RenderView.
+ auto guest_view_container = static_cast<ExtensionsGuestViewContainer*>(
+ guest_view::GuestViewContainer::FromID(element_instance_id));
+ if (!guest_view_container)
+ return;
+
+ guest_view_container->RegisterElementResizeCallback(
+ args[1].As<v8::Function>(), args.GetIsolate());
+
+ args.GetReturnValue().Set(v8::Boolean::New(context()->isolate(), true));
+}
+
+void GuestViewInternalCustomBindings::RegisterView(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // There are three parameters.
+ CHECK(args.Length() == 3);
+ // View Instance ID.
+ CHECK(args[0]->IsInt32());
+ // View element.
+ CHECK(args[1]->IsObject());
+ // View type (e.g. "webview").
+ CHECK(args[2]->IsString());
+
+ // A reference to the view object is stored in |weak_view_map| using its view
+ // ID as the key. The reference is made weak so that it will not extend the
+ // lifetime of the object.
+ int view_instance_id = args[0]->Int32Value();
+ auto object =
+ new v8::Global<v8::Object>(args.GetIsolate(), args[1].As<v8::Object>());
+ weak_view_map.Get().insert(std::make_pair(view_instance_id, object));
+
+ // The |view_instance_id| is given to the SetWeak callback so that that view's
+ // entry in |weak_view_map| can be cleared when the view object is garbage
+ // collected.
+ object->SetWeak(new int(view_instance_id),
+ &GuestViewInternalCustomBindings::ResetMapEntry,
+ v8::WeakCallbackType::kParameter);
+
+ // Let the GuestViewManager know that a GuestView has been created.
+ const std::string& view_type = *v8::String::Utf8Value(args[2]);
+ content::RenderThread::Get()->Send(
+ new GuestViewHostMsg_ViewCreated(view_instance_id, view_type));
+}
+
+void GuestViewInternalCustomBindings::RunWithGesture(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Gesture is required to request fullscreen.
+ blink::WebScopedUserGesture user_gesture;
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsFunction());
+ v8::Local<v8::Value> no_args;
+ context()->CallFunction(v8::Local<v8::Function>::Cast(args[0]), 0, &no_args);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.h b/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.h
new file mode 100644
index 00000000000..f18e15cd7ab
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.h
@@ -0,0 +1,98 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_GUEST_VIEW_GUEST_VIEW_INTERNAL_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_GUEST_VIEW_GUEST_VIEW_INTERNAL_CUSTOM_BINDINGS_H_
+
+#include <map>
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class Dispatcher;
+
+// Implements custom bindings for the guestViewInternal API.
+class GuestViewInternalCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit GuestViewInternalCustomBindings(ScriptContext* context);
+ ~GuestViewInternalCustomBindings() override;
+
+ private:
+ // ResetMapEntry is called as a callback to SetWeak(). It resets the
+ // weak view reference held in |view_map_|.
+ static void ResetMapEntry(const v8::WeakCallbackInfo<int>& data);
+
+ // AttachGuest attaches a GuestView to a provided container element. Once
+ // attached, the GuestView will participate in layout of the container page
+ // and become visible on screen.
+ // AttachGuest takes four parameters:
+ // |element_instance_id| uniquely identifies a container within the content
+ // module is able to host GuestViews.
+ // |guest_instance_id| uniquely identifies an unattached GuestView.
+ // |attach_params| is typically used to convey the current state of the
+ // container element at the time of attachment. These parameters are passed
+ // down to the GuestView. The GuestView may use these parameters to update the
+ // state of the guest hosted in another process.
+ // |callback| is an optional callback that is called once attachment is
+ // complete. The callback takes in a parameter for the WindowProxy of the
+ // guest identified by |guest_instance_id|.
+ void AttachGuest(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // DetachGuest detaches the container container specified from the associated
+ // GuestViewBase. DetachGuest takes two parameters:
+ // |element_instance_id| uniquely identifies a container within the content
+ // module is able to host GuestViews.
+ // |callback| is an optional callback that is called once the container has
+ // been detached.
+ void DetachGuest(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // AttachIframeGuest is --site-per-process variant of AttachGuest().
+ //
+ // AttachIframeGuest takes a |contentWindow| parameter in addition to the
+ // parameters to AttachGuest. That parameter is used to identify the
+ // RenderFrame of the <iframe> container element.
+ void AttachIframeGuest(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // GetContentWindow takes in a RenderView routing ID and returns the
+ // Window JavaScript object for that RenderView.
+ void GetContentWindow(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Destroys the GuestViewContainer given an element instance ID in |args|.
+ void DestroyContainer(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // GetViewFromID takes a view ID, and returns the GuestView element associated
+ // with that ID, if one exists. Otherwise, null is returned.
+ void GetViewFromID(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // RegisterDestructionCallback registers a JavaScript callback function to be
+ // called when the guestview's container is destroyed.
+ // RegisterDestructionCallback takes in a single paramater, |callback|.
+ void RegisterDestructionCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // RegisterElementResizeCallback registers a JavaScript callback function to
+ // be called when the element is resized. RegisterElementResizeCallback takes
+ // a single parameter, |callback|.
+ void RegisterElementResizeCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // RegisterView takes in a view ID and a GuestView element, and stores the
+ // pair as an entry in |view_map_|. The view can then be retrieved using
+ // GetViewFromID.
+ void RegisterView(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Runs a JavaScript function with user gesture.
+ //
+ // This is used to request webview element to enter fullscreen (from the
+ // embedder).
+ // Note that the guest requesting fullscreen means it has already been
+ // triggered by a user gesture and we get to this point if embedder allows
+ // the fullscreen request to proceed.
+ void RunWithGesture(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_GUEST_VIEW_GUEST_VIEW_INTERNAL_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/guest_view/mime_handler_view/OWNERS b/chromium/extensions/renderer/guest_view/mime_handler_view/OWNERS
new file mode 100644
index 00000000000..db781ac5adc
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/mime_handler_view/OWNERS
@@ -0,0 +1 @@
+raymes@chromium.org
diff --git a/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.cc b/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.cc
new file mode 100644
index 00000000000..91f8197e853
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.cc
@@ -0,0 +1,340 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h"
+
+#include <map>
+#include <set>
+
+#include "base/macros.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "components/guest_view/common/guest_view_messages.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/guest_view/extensions_guest_view_messages.h"
+#include "gin/arguments.h"
+#include "gin/dictionary.h"
+#include "gin/handle.h"
+#include "gin/interceptor.h"
+#include "gin/object_template_builder.h"
+#include "gin/wrappable.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebRemoteFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+namespace extensions {
+
+namespace {
+
+const char kPostMessageName[] = "postMessage";
+
+// The gin-backed scriptable object which is exposed by the BrowserPlugin for
+// MimeHandlerViewContainer. This currently only implements "postMessage".
+class ScriptableObject : public gin::Wrappable<ScriptableObject>,
+ public gin::NamedPropertyInterceptor {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ static v8::Local<v8::Object> Create(
+ v8::Isolate* isolate,
+ base::WeakPtr<MimeHandlerViewContainer> container) {
+ ScriptableObject* scriptable_object =
+ new ScriptableObject(isolate, container);
+ return gin::CreateHandle(isolate, scriptable_object)
+ .ToV8()
+ .As<v8::Object>();
+ }
+
+ // gin::NamedPropertyInterceptor
+ v8::Local<v8::Value> GetNamedProperty(
+ v8::Isolate* isolate,
+ const std::string& identifier) override {
+ if (identifier == kPostMessageName) {
+ if (post_message_function_template_.IsEmpty()) {
+ post_message_function_template_.Reset(
+ isolate,
+ gin::CreateFunctionTemplate(
+ isolate, base::Bind(&MimeHandlerViewContainer::PostMessage,
+ container_, isolate)));
+ }
+ v8::Local<v8::FunctionTemplate> function_template =
+ v8::Local<v8::FunctionTemplate>::New(isolate,
+ post_message_function_template_);
+ v8::Local<v8::Function> function;
+ if (function_template->GetFunction(isolate->GetCurrentContext())
+ .ToLocal(&function))
+ return function;
+ }
+ return v8::Local<v8::Value>();
+ }
+
+ private:
+ ScriptableObject(v8::Isolate* isolate,
+ base::WeakPtr<MimeHandlerViewContainer> container)
+ : gin::NamedPropertyInterceptor(isolate, this),
+ container_(container) {}
+
+ // gin::Wrappable
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override {
+ return gin::Wrappable<ScriptableObject>::GetObjectTemplateBuilder(isolate)
+ .AddNamedPropertyInterceptor();
+ }
+
+ base::WeakPtr<MimeHandlerViewContainer> container_;
+ v8::Persistent<v8::FunctionTemplate> post_message_function_template_;
+};
+
+// static
+gin::WrapperInfo ScriptableObject::kWrapperInfo = { gin::kEmbedderNativeGin };
+
+// Maps from content::RenderFrame to the set of MimeHandlerViewContainers within
+// it.
+base::LazyInstance<
+ std::map<content::RenderFrame*, std::set<MimeHandlerViewContainer*>>>
+ g_mime_handler_view_container_map = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+MimeHandlerViewContainer::MimeHandlerViewContainer(
+ content::RenderFrame* render_frame,
+ const std::string& mime_type,
+ const GURL& original_url)
+ : GuestViewContainer(render_frame),
+ mime_type_(mime_type),
+ original_url_(original_url),
+ guest_proxy_routing_id_(-1),
+ guest_loaded_(false),
+ weak_factory_(this) {
+ DCHECK(!mime_type_.empty());
+ is_embedded_ = !render_frame->GetWebFrame()->document().isPluginDocument();
+ g_mime_handler_view_container_map.Get()[render_frame].insert(this);
+}
+
+MimeHandlerViewContainer::~MimeHandlerViewContainer() {
+ if (loader_)
+ loader_->cancel();
+
+ if (render_frame()) {
+ g_mime_handler_view_container_map.Get()[render_frame()].erase(this);
+ if (g_mime_handler_view_container_map.Get()[render_frame()].empty())
+ g_mime_handler_view_container_map.Get().erase(render_frame());
+ }
+}
+
+// static
+std::vector<MimeHandlerViewContainer*>
+MimeHandlerViewContainer::FromRenderFrame(content::RenderFrame* render_frame) {
+ auto it = g_mime_handler_view_container_map.Get().find(render_frame);
+ if (it == g_mime_handler_view_container_map.Get().end())
+ return std::vector<MimeHandlerViewContainer*>();
+
+ return std::vector<MimeHandlerViewContainer*>(it->second.begin(),
+ it->second.end());
+}
+
+void MimeHandlerViewContainer::OnReady() {
+ if (!render_frame())
+ return;
+
+ blink::WebFrame* frame = render_frame()->GetWebFrame();
+ blink::WebURLLoaderOptions options;
+ // The embedded plugin is allowed to be cross-origin and we should always
+ // send credentials/cookies with the request.
+ options.crossOriginRequestPolicy =
+ blink::WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
+ options.allowCredentials = true;
+ DCHECK(!loader_);
+ loader_.reset(frame->createAssociatedURLLoader(options));
+
+ blink::WebURLRequest request(original_url_);
+ request.setRequestContext(blink::WebURLRequest::RequestContextObject);
+ loader_->loadAsynchronously(request, this);
+}
+
+bool MimeHandlerViewContainer::OnMessage(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(MimeHandlerViewContainer, message)
+ IPC_MESSAGE_HANDLER(ExtensionsGuestViewMsg_CreateMimeHandlerViewGuestACK,
+ OnCreateMimeHandlerViewGuestACK)
+ IPC_MESSAGE_HANDLER(
+ ExtensionsGuestViewMsg_MimeHandlerViewGuestOnLoadCompleted,
+ OnMimeHandlerViewGuestOnLoadCompleted)
+ IPC_MESSAGE_HANDLER(GuestViewMsg_GuestAttached, OnGuestAttached)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void MimeHandlerViewContainer::DidFinishLoading() {
+ DCHECK(!is_embedded_);
+ CreateMimeHandlerViewGuest();
+}
+
+void MimeHandlerViewContainer::OnRenderFrameDestroyed() {
+ g_mime_handler_view_container_map.Get().erase(render_frame());
+}
+
+void MimeHandlerViewContainer::DidReceiveData(const char* data,
+ int data_length) {
+ view_id_ += std::string(data, data_length);
+}
+
+
+void MimeHandlerViewContainer::DidResizeElement(const gfx::Size& new_size) {
+ element_size_ = new_size;
+ render_frame()->Send(new ExtensionsGuestViewHostMsg_ResizeGuest(
+ render_frame()->GetRoutingID(), element_instance_id(), new_size));
+}
+
+v8::Local<v8::Object> MimeHandlerViewContainer::V8ScriptableObject(
+ v8::Isolate* isolate) {
+ if (scriptable_object_.IsEmpty()) {
+ v8::Local<v8::Object> object =
+ ScriptableObject::Create(isolate, weak_factory_.GetWeakPtr());
+ scriptable_object_.Reset(isolate, object);
+ }
+ return v8::Local<v8::Object>::New(isolate, scriptable_object_);
+}
+
+void MimeHandlerViewContainer::didReceiveData(blink::WebURLLoader* /* unused */,
+ const char* data,
+ int data_length,
+ int /* unused */) {
+ view_id_ += std::string(data, data_length);
+}
+
+void MimeHandlerViewContainer::didFinishLoading(
+ blink::WebURLLoader* /* unused */,
+ double /* unused */,
+ int64_t /* unused */) {
+ DCHECK(is_embedded_);
+ CreateMimeHandlerViewGuest();
+}
+
+void MimeHandlerViewContainer::PostMessage(v8::Isolate* isolate,
+ v8::Local<v8::Value> message) {
+ if (!guest_loaded_) {
+ linked_ptr<v8::Global<v8::Value>> global(
+ new v8::Global<v8::Value>(isolate, message));
+ pending_messages_.push_back(global);
+ return;
+ }
+
+ content::RenderView* guest_proxy_render_view =
+ content::RenderView::FromRoutingID(guest_proxy_routing_id_);
+ if (!guest_proxy_render_view)
+ return;
+ blink::WebFrame* guest_proxy_frame =
+ guest_proxy_render_view->GetWebView()->mainFrame();
+ if (!guest_proxy_frame)
+ return;
+
+ v8::Context::Scope context_scope(
+ render_frame()->GetWebFrame()->mainWorldScriptContext());
+
+ // TODO(lazyboy,nasko): The WebLocalFrame branch is not used when running
+ // on top of out-of-process iframes. Remove it once the code is converted.
+ v8::Local<v8::Object> guest_proxy_window;
+ if (guest_proxy_frame->isWebLocalFrame()) {
+ guest_proxy_window =
+ guest_proxy_frame->mainWorldScriptContext()->Global();
+ } else {
+ guest_proxy_window = guest_proxy_frame->toWebRemoteFrame()
+ ->deprecatedMainWorldScriptContext()
+ ->Global();
+ }
+ gin::Dictionary window_object(isolate, guest_proxy_window);
+ v8::Local<v8::Function> post_message;
+ if (!window_object.Get(std::string(kPostMessageName), &post_message))
+ return;
+
+ v8::Local<v8::Value> args[] = {
+ message,
+ // Post the message to any domain inside the browser plugin. The embedder
+ // should already know what is embedded.
+ gin::StringToV8(isolate, "*")};
+ render_frame()->GetWebFrame()->callFunctionEvenIfScriptDisabled(
+ post_message.As<v8::Function>(),
+ guest_proxy_window,
+ arraysize(args),
+ args);
+}
+
+void MimeHandlerViewContainer::PostMessageFromValue(
+ const base::Value& message) {
+ blink::WebFrame* frame = render_frame()->GetWebFrame();
+ if (!frame)
+ return;
+
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(frame->mainWorldScriptContext());
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ PostMessage(isolate,
+ converter->ToV8Value(&message, frame->mainWorldScriptContext()));
+}
+
+void MimeHandlerViewContainer::OnCreateMimeHandlerViewGuestACK(
+ int element_instance_id) {
+ DCHECK_NE(this->element_instance_id(), guest_view::kInstanceIDNone);
+ DCHECK_EQ(this->element_instance_id(), element_instance_id);
+
+ if (!render_frame())
+ return;
+
+ render_frame()->AttachGuest(element_instance_id);
+}
+
+void MimeHandlerViewContainer::OnGuestAttached(int /* unused */,
+ int guest_proxy_routing_id) {
+ // Save the RenderView routing ID of the guest here so it can be used to route
+ // PostMessage calls.
+ guest_proxy_routing_id_ = guest_proxy_routing_id;
+}
+
+void MimeHandlerViewContainer::OnMimeHandlerViewGuestOnLoadCompleted(
+ int /* unused */) {
+ if (!render_frame())
+ return;
+
+ guest_loaded_ = true;
+ if (pending_messages_.empty())
+ return;
+
+ // Now that the guest has loaded, flush any unsent messages.
+ blink::WebFrame* frame = render_frame()->GetWebFrame();
+ if (!frame)
+ return;
+
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(frame->mainWorldScriptContext());
+ for (const auto& pending_message : pending_messages_)
+ PostMessage(isolate, v8::Local<v8::Value>::New(isolate, *pending_message));
+
+ pending_messages_.clear();
+}
+
+void MimeHandlerViewContainer::CreateMimeHandlerViewGuest() {
+ // The loader has completed loading |view_id_| so we can dispose it.
+ loader_.reset();
+
+ DCHECK_NE(element_instance_id(), guest_view::kInstanceIDNone);
+
+ if (!render_frame())
+ return;
+
+ render_frame()->Send(
+ new ExtensionsGuestViewHostMsg_CreateMimeHandlerViewGuest(
+ render_frame()->GetRoutingID(), view_id_, element_instance_id(),
+ element_size_));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h b/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h
new file mode 100644
index 00000000000..6c98abefabf
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h
@@ -0,0 +1,132 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_CONTAINER_H_
+#define EXTENSIONS_RENDERER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_CONTAINER_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/guest_view/renderer/guest_view_container.h"
+#include "third_party/WebKit/public/platform/WebURLLoader.h"
+#include "third_party/WebKit/public/platform/WebURLLoaderClient.h"
+#include "ui/gfx/geometry/size.h"
+#include "url/gurl.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// A container for loading up an extension inside a BrowserPlugin to handle a
+// MIME type. A request for the URL of the data to load inside the container is
+// made and a url is sent back in response which points to the URL which the
+// container should be navigated to. There are two cases for making this URL
+// request, the case where the plugin is embedded and the case where it is top
+// level:
+// 1) In the top level case a URL request for the data to load has already been
+// made by the renderer on behalf of the plugin. The |DidReceiveData| and
+// |DidFinishLoading| callbacks (from BrowserPluginDelegate) will be called
+// when data is received and when it has finished being received,
+// respectively.
+// 2) In the embedded case, no URL request is automatically made by the
+// renderer. We make a URL request for the data inside the container using
+// a WebURLLoader. In this case, the |didReceiveData| and |didFinishLoading|
+// (from WebURLLoaderClient) when data is received and when it has finished
+// being received.
+class MimeHandlerViewContainer : public guest_view::GuestViewContainer,
+ public blink::WebURLLoaderClient {
+ public:
+ MimeHandlerViewContainer(content::RenderFrame* render_frame,
+ const std::string& mime_type,
+ const GURL& original_url);
+
+ static std::vector<MimeHandlerViewContainer*> FromRenderFrame(
+ content::RenderFrame* render_frame);
+
+ // GuestViewContainer implementation.
+ bool OnMessage(const IPC::Message& message) override;
+ void OnReady() override;
+
+ // BrowserPluginDelegate implementation.
+ void DidFinishLoading() override;
+ void DidReceiveData(const char* data, int data_length) override;
+ void DidResizeElement(const gfx::Size& new_size) override;
+ v8::Local<v8::Object> V8ScriptableObject(v8::Isolate*) override;
+
+ // WebURLLoaderClient overrides.
+ void didReceiveData(blink::WebURLLoader* loader,
+ const char* data,
+ int data_length,
+ int encoded_data_length) override;
+ void didFinishLoading(blink::WebURLLoader* loader,
+ double finish_time,
+ int64_t total_encoded_data_length) override;
+
+ // GuestViewContainer overrides.
+ void OnRenderFrameDestroyed() override;
+
+ // Post a JavaScript message to the guest.
+ void PostMessage(v8::Isolate* isolate, v8::Local<v8::Value> message);
+
+ // Post |message| to the guest.
+ void PostMessageFromValue(const base::Value& message);
+
+ protected:
+ ~MimeHandlerViewContainer() override;
+
+ private:
+ // Message handlers.
+ void OnCreateMimeHandlerViewGuestACK(int element_instance_id);
+ void OnGuestAttached(int element_instance_id,
+ int guest_proxy_routing_id);
+ void OnMimeHandlerViewGuestOnLoadCompleted(int element_instance_id);
+
+ void CreateMimeHandlerViewGuest();
+
+ // The MIME type of the plugin.
+ const std::string mime_type_;
+
+ // The URL of the extension to navigate to.
+ std::string view_id_;
+
+ // Whether the plugin is embedded or not.
+ bool is_embedded_;
+
+ // The original URL of the plugin.
+ GURL original_url_;
+
+ // The RenderView routing ID of the guest.
+ int guest_proxy_routing_id_;
+
+ // A URL loader to load the |original_url_| when the plugin is embedded. In
+ // the embedded case, no URL request is made automatically.
+ scoped_ptr<blink::WebURLLoader> loader_;
+
+ // The scriptable object that backs the plugin.
+ v8::Global<v8::Object> scriptable_object_;
+
+ // Pending postMessage messages that need to be sent to the guest. These are
+ // queued while the guest is loading and once it is fully loaded they are
+ // delivered so that messages aren't lost.
+ std::vector<linked_ptr<v8::Global<v8::Value>>> pending_messages_;
+
+ // True if the guest page has fully loaded and its JavaScript onload function
+ // has been called.
+ bool guest_loaded_;
+
+ // The size of the element.
+ gfx::Size element_size_;
+
+ base::WeakPtrFactory<MimeHandlerViewContainer> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MimeHandlerViewContainer);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_CONTAINER_H_
diff --git a/chromium/extensions/renderer/i18n_custom_bindings.cc b/chromium/extensions/renderer/i18n_custom_bindings.cc
new file mode 100644
index 00000000000..08d9e75aac3
--- /dev/null
+++ b/chromium/extensions/renderer/i18n_custom_bindings.cc
@@ -0,0 +1,245 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/i18n_custom_bindings.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/message_bundle.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "third_party/cld_2/src/public/compact_lang_det.h"
+#include "third_party/cld_2/src/public/encodings.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+namespace {
+
+// Max number of languages detected by CLD2.
+const int kCldNumLangs = 3;
+
+struct DetectedLanguage {
+ DetectedLanguage(const std::string& language, int percentage)
+ : language(language), percentage(percentage) {}
+ ~DetectedLanguage() {}
+
+ // Returns a new v8::Local<v8::Value> representing the serialized form of
+ // this DetectedLanguage object.
+ scoped_ptr<base::DictionaryValue> ToDictionary() const;
+
+ std::string language;
+ int percentage;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DetectedLanguage);
+};
+
+// LanguageDetectionResult object that holds detected langugae reliability and
+// array of DetectedLanguage
+struct LanguageDetectionResult {
+ explicit LanguageDetectionResult(bool is_reliable)
+ : is_reliable(is_reliable) {}
+ ~LanguageDetectionResult() {}
+
+ // Returns a new v8::Local<v8::Value> representing the serialized form of
+ // this Result object.
+ v8::Local<v8::Value> ToValue(ScriptContext* context);
+
+ // CLD detected language reliability
+ bool is_reliable;
+
+ // Array of detectedLanguage of size 1-3. The null is returned if
+ // there were no languages detected
+ std::vector<scoped_ptr<DetectedLanguage>> languages;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LanguageDetectionResult);
+};
+
+scoped_ptr<base::DictionaryValue> DetectedLanguage::ToDictionary() const {
+ scoped_ptr<base::DictionaryValue> dict_value(new base::DictionaryValue());
+ dict_value->SetString("language", language.c_str());
+ dict_value->SetInteger("percentage", percentage);
+ return dict_value;
+}
+
+v8::Local<v8::Value> LanguageDetectionResult::ToValue(ScriptContext* context) {
+ base::DictionaryValue dict_value;
+ dict_value.SetBoolean("isReliable", is_reliable);
+ scoped_ptr<base::ListValue> languages_list(new base::ListValue());
+ for (const auto& language : languages)
+ languages_list->Append(language->ToDictionary());
+ dict_value.Set("languages", std::move(languages_list));
+
+ v8::Local<v8::Context> v8_context = context->v8_context();
+ v8::Isolate* isolate = v8_context->GetIsolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ v8::Local<v8::Value> result = converter->ToV8Value(&dict_value, v8_context);
+ return handle_scope.Escape(result);
+}
+
+void InitDetectedLanguages(
+ CLD2::Language* languages,
+ int* percents,
+ std::vector<scoped_ptr<DetectedLanguage>>* detected_languages) {
+ for (int i = 0; i < kCldNumLangs; i++) {
+ std::string language_code;
+ // Convert LanguageCode 'zh' to 'zh-CN' and 'zh-Hant' to 'zh-TW' for
+ // Translate server usage. see DetermineTextLanguage in
+ // components/translate/core/language_detection/language_detection_util.cc
+ if (languages[i] == CLD2::UNKNOWN_LANGUAGE) {
+ // Break from the loop since there is no need to save
+ // unknown languages
+ break;
+ } else {
+ language_code =
+ CLD2::LanguageCode(static_cast<CLD2::Language>(languages[i]));
+ }
+ detected_languages->push_back(
+ make_scoped_ptr(new DetectedLanguage(language_code, percents[i])));
+ }
+}
+
+} // namespace
+
+I18NCustomBindings::I18NCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetL10nMessage",
+ base::Bind(&I18NCustomBindings::GetL10nMessage, base::Unretained(this)));
+ RouteFunction("GetL10nUILanguage",
+ base::Bind(&I18NCustomBindings::GetL10nUILanguage,
+ base::Unretained(this)));
+ RouteFunction("DetectTextLanguage",
+ base::Bind(&I18NCustomBindings::DetectTextLanguage,
+ base::Unretained(this)));
+}
+
+void I18NCustomBindings::GetL10nMessage(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 3 || !args[0]->IsString()) {
+ NOTREACHED() << "Bad arguments";
+ return;
+ }
+
+ std::string extension_id;
+ if (args[2]->IsNull() || !args[2]->IsString()) {
+ return;
+ } else {
+ extension_id = *v8::String::Utf8Value(args[2]);
+ if (extension_id.empty())
+ return;
+ }
+
+ L10nMessagesMap* l10n_messages = GetL10nMessagesMap(extension_id);
+ if (!l10n_messages) {
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (!render_frame)
+ return;
+
+ L10nMessagesMap messages;
+ // A sync call to load message catalogs for current extension.
+ render_frame->Send(
+ new ExtensionHostMsg_GetMessageBundle(extension_id, &messages));
+
+ // Save messages we got.
+ ExtensionToL10nMessagesMap& l10n_messages_map =
+ *GetExtensionToL10nMessagesMap();
+ l10n_messages_map[extension_id] = messages;
+
+ l10n_messages = GetL10nMessagesMap(extension_id);
+ }
+
+ std::string message_name = *v8::String::Utf8Value(args[0]);
+ std::string message =
+ MessageBundle::GetL10nMessage(message_name, *l10n_messages);
+
+ v8::Isolate* isolate = args.GetIsolate();
+ std::vector<std::string> substitutions;
+ if (args[1]->IsArray()) {
+ // chrome.i18n.getMessage("message_name", ["more", "params"]);
+ v8::Local<v8::Array> placeholders = v8::Local<v8::Array>::Cast(args[1]);
+ uint32_t count = placeholders->Length();
+ if (count > 9)
+ return;
+ for (uint32_t i = 0; i < count; ++i) {
+ substitutions.push_back(*v8::String::Utf8Value(placeholders->Get(
+ v8::Integer::New(isolate, i))));
+ }
+ } else if (args[1]->IsString()) {
+ // chrome.i18n.getMessage("message_name", "one param");
+ substitutions.push_back(*v8::String::Utf8Value(args[1]));
+ }
+
+ args.GetReturnValue().Set(v8::String::NewFromUtf8(
+ isolate,
+ base::ReplaceStringPlaceholders(message, substitutions, NULL).c_str()));
+}
+
+void I18NCustomBindings::GetL10nUILanguage(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(v8::String::NewFromUtf8(
+ args.GetIsolate(), content::RenderThread::Get()->GetLocale().c_str()));
+}
+
+void I18NCustomBindings::DetectTextLanguage(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 1);
+ CHECK(args[0]->IsString());
+
+ std::string text = *v8::String::Utf8Value(args[0]);
+ CLD2::CLDHints cldhints = {nullptr, "", CLD2::UNKNOWN_ENCODING,
+ CLD2::UNKNOWN_LANGUAGE};
+
+ bool is_plain_text = true; // assume the text is a plain text
+ int flags = 0; // no flags, see compact_lang_det.h for details
+ int text_bytes; // amount of non-tag/letters-only text (assumed 0)
+ int valid_prefix_bytes; // amount of valid UTF8 character in the string
+ double normalized_score[kCldNumLangs];
+
+ CLD2::Language languages[kCldNumLangs];
+ int percents[kCldNumLangs];
+ bool is_reliable = false;
+
+ // populating languages and percents
+ int cld_language = CLD2::ExtDetectLanguageSummaryCheckUTF8(
+ text.c_str(), static_cast<int>(text.size()), is_plain_text, &cldhints,
+ flags, languages, percents, normalized_score,
+ nullptr, // assumed no ResultChunkVector is used
+ &text_bytes, &is_reliable, &valid_prefix_bytes);
+
+ // Check if non-UTF8 character is encountered
+ // See bug http://crbug.com/444258.
+ if (valid_prefix_bytes < static_cast<int>(text.size()) &&
+ cld_language == CLD2::UNKNOWN_LANGUAGE) {
+ // Detect Language upto before the first non-UTF8 character
+ CLD2::ExtDetectLanguageSummary(
+ text.c_str(), valid_prefix_bytes, is_plain_text, &cldhints, flags,
+ languages, percents, normalized_score,
+ nullptr, // assumed no ResultChunkVector is used
+ &text_bytes, &is_reliable);
+ }
+
+ LanguageDetectionResult result(is_reliable);
+ // populate LanguageDetectionResult with languages and percents
+ InitDetectedLanguages(languages, percents, &result.languages);
+
+ args.GetReturnValue().Set(result.ToValue(context()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/i18n_custom_bindings.h b/chromium/extensions/renderer/i18n_custom_bindings.h
new file mode 100644
index 00000000000..0ad2ffcdee9
--- /dev/null
+++ b/chromium/extensions/renderer/i18n_custom_bindings.h
@@ -0,0 +1,26 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_I18N_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_I18N_CUSTOM_BINDINGS_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Implements custom bindings for the i18n API.
+class I18NCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit I18NCustomBindings(ScriptContext* context);
+
+ private:
+ void GetL10nMessage(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetL10nUILanguage(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void DetectTextLanguage(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_I18N_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/id_generator_custom_bindings.cc b/chromium/extensions/renderer/id_generator_custom_bindings.cc
new file mode 100644
index 00000000000..182c4540d16
--- /dev/null
+++ b/chromium/extensions/renderer/id_generator_custom_bindings.cc
@@ -0,0 +1,31 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/id_generator_custom_bindings.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+
+namespace extensions {
+
+IdGeneratorCustomBindings::IdGeneratorCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("GetNextId",
+ base::Bind(&IdGeneratorCustomBindings::GetNextId,
+ base::Unretained(this)));
+}
+
+void IdGeneratorCustomBindings::GetNextId(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ static int32_t next_id = 0;
+ ++next_id;
+ // Make sure 0 is never returned because some APIs (particularly WebRequest)
+ // have special meaning for 0 IDs.
+ if (next_id == 0)
+ next_id = 1;
+ args.GetReturnValue().Set(next_id);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/id_generator_custom_bindings.h b/chromium/extensions/renderer/id_generator_custom_bindings.h
new file mode 100644
index 00000000000..b8d79f6c6be
--- /dev/null
+++ b/chromium/extensions/renderer/id_generator_custom_bindings.h
@@ -0,0 +1,25 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_ID_GENERATOR_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_ID_GENERATOR_CUSTOM_BINDINGS_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Implements function that can be used by JS layer to generate unique integer
+// identifiers.
+class IdGeneratorCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ IdGeneratorCustomBindings(ScriptContext* context);
+
+ private:
+ void GetNextId(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_ID_GENERATOR_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/injection_host.cc b/chromium/extensions/renderer/injection_host.cc
new file mode 100644
index 00000000000..30e8679de88
--- /dev/null
+++ b/chromium/extensions/renderer/injection_host.cc
@@ -0,0 +1,12 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/injection_host.h"
+
+InjectionHost::InjectionHost(const HostID& host_id) :
+ id_(host_id) {
+}
+
+InjectionHost::~InjectionHost() {
+}
diff --git a/chromium/extensions/renderer/injection_host.h b/chromium/extensions/renderer/injection_host.h
new file mode 100644
index 00000000000..21a4c0ed61a
--- /dev/null
+++ b/chromium/extensions/renderer/injection_host.h
@@ -0,0 +1,47 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_INJECTION_HOST_H
+#define EXTENSIONS_RENDERER_INJECTION_HOST_H
+
+#include "base/macros.h"
+#include "extensions/common/host_id.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "url/gurl.h"
+
+namespace content {
+class RenderFrame;
+}
+
+// An interface for all kinds of hosts who own user scripts.
+class InjectionHost {
+ public:
+ InjectionHost(const HostID& host_id);
+ virtual ~InjectionHost();
+
+ virtual std::string GetContentSecurityPolicy() const = 0;
+
+ // The base url for the host.
+ virtual const GURL& url() const = 0;
+
+ // The human-readable name of the host.
+ virtual const std::string& name() const = 0;
+
+ // Returns true if the script should execute.
+ virtual extensions::PermissionsData::AccessType CanExecuteOnFrame(
+ const GURL& document_url,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ bool is_declarative) const = 0;
+
+ const HostID& id() const { return id_; }
+
+ private:
+ // The ID of the host.
+ HostID id_;
+
+ DISALLOW_COPY_AND_ASSIGN(InjectionHost);
+};
+
+#endif // EXTENSIONS_RENDERER_INJECTION_HOST_H
diff --git a/chromium/extensions/renderer/json_schema_unittest.cc b/chromium/extensions/renderer/json_schema_unittest.cc
new file mode 100644
index 00000000000..3a2191cad0f
--- /dev/null
+++ b/chromium/extensions/renderer/json_schema_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/module_system_test.h"
+#include "extensions/renderer/v8_schema_registry.h"
+#include "gin/dictionary.h"
+#include "grit/extensions_renderer_resources.h"
+
+namespace extensions {
+
+class JsonSchemaTest : public ModuleSystemTest {
+ public:
+ void SetUp() override {
+ ModuleSystemTest::SetUp();
+
+ env()->RegisterModule("json_schema", IDR_JSON_SCHEMA_JS);
+ env()->RegisterModule("utils", IDR_UTILS_JS);
+
+ env()->module_system()->RegisterNativeHandler(
+ "schema_registry", schema_registry_.AsNativeHandler());
+
+ env()->RegisterTestFile("json_schema_test", "json_schema_test.js");
+ }
+
+ protected:
+ void TestFunction(const std::string& test_name) {
+ env()->module_system()->CallModuleMethod("json_schema_test", test_name);
+ }
+
+ private:
+ V8SchemaRegistry schema_registry_;
+};
+
+TEST_F(JsonSchemaTest, TestFormatError) {
+ TestFunction("testFormatError");
+}
+
+TEST_F(JsonSchemaTest, TestComplex) {
+ TestFunction("testComplex");
+}
+
+TEST_F(JsonSchemaTest, TestEnum) {
+ TestFunction("testEnum");
+}
+
+TEST_F(JsonSchemaTest, TestExtends) {
+ TestFunction("testExtends");
+}
+
+TEST_F(JsonSchemaTest, TestObject) {
+ TestFunction("testObject");
+}
+
+TEST_F(JsonSchemaTest, TestArrayTuple) {
+ TestFunction("testArrayTuple");
+}
+
+TEST_F(JsonSchemaTest, TestArrayNonTuple) {
+ TestFunction("testArrayNonTuple");
+}
+
+TEST_F(JsonSchemaTest, TestString) {
+ TestFunction("testString");
+}
+
+TEST_F(JsonSchemaTest, TestNumber) {
+ TestFunction("testNumber");
+}
+
+TEST_F(JsonSchemaTest, TestIntegerBounds) {
+ TestFunction("testIntegerBounds");
+}
+
+TEST_F(JsonSchemaTest, TestType) {
+ gin::Dictionary array_buffer_container(
+ env()->isolate(),
+ env()->CreateGlobal("otherContextArrayBufferContainer"));
+ {
+ // Create an ArrayBuffer in another v8 context and pass it to the test
+ // through a global.
+ scoped_ptr<ModuleSystemTestEnvironment> other_env(CreateEnvironment());
+ v8::Context::Scope scope(other_env->context()->v8_context());
+ v8::Local<v8::ArrayBuffer> array_buffer(
+ v8::ArrayBuffer::New(env()->isolate(), 1));
+ array_buffer_container.Set("value", array_buffer);
+ }
+ TestFunction("testType");
+}
+
+TEST_F(JsonSchemaTest, TestTypeReference) {
+ TestFunction("testTypeReference");
+}
+
+TEST_F(JsonSchemaTest, TestGetAllTypesForSchema) {
+ TestFunction("testGetAllTypesForSchema");
+}
+
+TEST_F(JsonSchemaTest, TestIsValidSchemaType) {
+ TestFunction("testIsValidSchemaType");
+}
+
+TEST_F(JsonSchemaTest, TestCheckSchemaOverlap) {
+ TestFunction("testCheckSchemaOverlap");
+}
+
+TEST_F(JsonSchemaTest, TestInstanceOf) {
+ TestFunction("testInstanceOf");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/lazy_background_page_native_handler.cc b/chromium/extensions/renderer/lazy_background_page_native_handler.cc
new file mode 100644
index 00000000000..f09beec7a7f
--- /dev/null
+++ b/chromium/extensions/renderer/lazy_background_page_native_handler.cc
@@ -0,0 +1,46 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/lazy_background_page_native_handler.h"
+
+#include "base/bind.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+LazyBackgroundPageNativeHandler::LazyBackgroundPageNativeHandler(
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "IncrementKeepaliveCount",
+ base::Bind(&LazyBackgroundPageNativeHandler::IncrementKeepaliveCount,
+ base::Unretained(this)));
+ RouteFunction(
+ "DecrementKeepaliveCount",
+ base::Bind(&LazyBackgroundPageNativeHandler::DecrementKeepaliveCount,
+ base::Unretained(this)));
+}
+
+void LazyBackgroundPageNativeHandler::IncrementKeepaliveCount(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (context() && ExtensionFrameHelper::IsContextForEventPage(context())) {
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ render_frame->Send(new ExtensionHostMsg_IncrementLazyKeepaliveCount(
+ render_frame->GetRoutingID()));
+ }
+}
+
+void LazyBackgroundPageNativeHandler::DecrementKeepaliveCount(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (context() && ExtensionFrameHelper::IsContextForEventPage(context())) {
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ render_frame->Send(new ExtensionHostMsg_DecrementLazyKeepaliveCount(
+ render_frame->GetRoutingID()));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/lazy_background_page_native_handler.h b/chromium/extensions/renderer/lazy_background_page_native_handler.h
new file mode 100644
index 00000000000..88eaf0ffb1f
--- /dev/null
+++ b/chromium/extensions/renderer/lazy_background_page_native_handler.h
@@ -0,0 +1,23 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_LAZY_BACKGROUND_PAGE_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_LAZY_BACKGROUND_PAGE_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class Extension;
+
+class LazyBackgroundPageNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit LazyBackgroundPageNativeHandler(ScriptContext* context);
+ void IncrementKeepaliveCount(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void DecrementKeepaliveCount(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_LAZY_BACKGROUND_PAGE_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/logging_native_handler.cc b/chromium/extensions/renderer/logging_native_handler.cc
new file mode 100644
index 00000000000..86fbc9c9fbe
--- /dev/null
+++ b/chromium/extensions/renderer/logging_native_handler.cc
@@ -0,0 +1,78 @@
+// Copyright 2014 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.
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/renderer/logging_native_handler.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+LoggingNativeHandler::LoggingNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "DCHECK",
+ base::Bind(&LoggingNativeHandler::Dcheck, base::Unretained(this)));
+ RouteFunction(
+ "CHECK",
+ base::Bind(&LoggingNativeHandler::Check, base::Unretained(this)));
+ RouteFunction(
+ "DCHECK_IS_ON",
+ base::Bind(&LoggingNativeHandler::DcheckIsOn, base::Unretained(this)));
+ RouteFunction("LOG",
+ base::Bind(&LoggingNativeHandler::Log, base::Unretained(this)));
+ RouteFunction(
+ "WARNING",
+ base::Bind(&LoggingNativeHandler::Warning, base::Unretained(this)));
+}
+
+LoggingNativeHandler::~LoggingNativeHandler() {}
+
+void LoggingNativeHandler::Check(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ bool check_value;
+ std::string error_message;
+ ParseArgs(args, &check_value, &error_message);
+ CHECK(check_value) << error_message;
+}
+
+void LoggingNativeHandler::Dcheck(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ bool check_value;
+ std::string error_message;
+ ParseArgs(args, &check_value, &error_message);
+ DCHECK(check_value) << error_message;
+}
+
+void LoggingNativeHandler::DcheckIsOn(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(DCHECK_IS_ON());
+}
+
+void LoggingNativeHandler::Log(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ LOG(INFO) << *v8::String::Utf8Value(args[0]);
+}
+
+void LoggingNativeHandler::Warning(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ LOG(WARNING) << *v8::String::Utf8Value(args[0]);
+}
+
+void LoggingNativeHandler::ParseArgs(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ bool* check_value,
+ std::string* error_message) {
+ CHECK_LE(args.Length(), 2);
+ *check_value = args[0]->BooleanValue();
+ if (args.Length() == 2) {
+ *error_message = "Error: " + std::string(*v8::String::Utf8Value(args[1]));
+ }
+
+ *error_message += "\n" + context()->GetStackTraceAsString();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/logging_native_handler.h b/chromium/extensions/renderer/logging_native_handler.h
new file mode 100644
index 00000000000..ca9938c2760
--- /dev/null
+++ b/chromium/extensions/renderer/logging_native_handler.h
@@ -0,0 +1,53 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_LOGGING_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_LOGGING_NATIVE_HANDLER_H_
+
+#include <string>
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Exposes logging.h macros to JavaScript bindings.
+class LoggingNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit LoggingNativeHandler(ScriptContext* context);
+ ~LoggingNativeHandler() override;
+
+ // Equivalent to CHECK(predicate) << message.
+ //
+ // void(predicate, message?)
+ void Check(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Equivalent to DCHECK(predicate) << message.
+ //
+ // void(predicate, message?)
+ void Dcheck(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Equivalent to DCHECK_IS_ON().
+ //
+ // bool()
+ void DcheckIsOn(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Equivalent to LOG(INFO) << message.
+ //
+ // void(message)
+ void Log(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Equivalent to LOG(WARNING) << message.
+ //
+ // void(message)
+ void Warning(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ void ParseArgs(const v8::FunctionCallbackInfo<v8::Value>& args,
+ bool* check_value,
+ std::string* error_message);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_LOGGING_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/messaging_bindings.cc b/chromium/extensions/renderer/messaging_bindings.cc
new file mode 100644
index 00000000000..95bf59a4b67
--- /dev/null
+++ b/chromium/extensions/renderer/messaging_bindings.cc
@@ -0,0 +1,492 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/messaging_bindings.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/values.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/common/child_process_host.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_handlers/externally_connectable.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/event_bindings.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/gc_callback.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebScopedWindowFocusAllowedIndicator.h"
+#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
+#include "v8/include/v8.h"
+
+// Message passing API example (in a content script):
+// var extension =
+// new chrome.Extension('00123456789abcdef0123456789abcdef0123456');
+// var port = runtime.connect();
+// port.postMessage('Can you hear me now?');
+// port.onmessage.addListener(function(msg, port) {
+// alert('response=' + msg);
+// port.postMessage('I got your reponse');
+// });
+
+using content::RenderThread;
+using content::V8ValueConverter;
+
+namespace extensions {
+
+using v8_helpers::ToV8String;
+using v8_helpers::ToV8StringUnsafe;
+using v8_helpers::IsEmptyOrUndefied;
+
+namespace {
+
+// Tracks every reference between ScriptContexts and Ports, by ID.
+class PortTracker {
+ public:
+ PortTracker() {}
+ ~PortTracker() {}
+
+ // Returns true if |context| references |port_id|.
+ bool HasReference(ScriptContext* context, int port_id) const {
+ auto ports = contexts_to_ports_.find(context);
+ return ports != contexts_to_ports_.end() &&
+ ports->second.count(port_id) > 0;
+ }
+
+ // Marks |context| and |port_id| as referencing each other.
+ void AddReference(ScriptContext* context, int port_id) {
+ contexts_to_ports_[context].insert(port_id);
+ }
+
+ // Removes the references between |context| and |port_id|.
+ // Returns true if a reference was removed, false if the reference didn't
+ // exist to be removed.
+ bool RemoveReference(ScriptContext* context, int port_id) {
+ auto ports = contexts_to_ports_.find(context);
+ if (ports == contexts_to_ports_.end() ||
+ ports->second.erase(port_id) == 0) {
+ return false;
+ }
+ if (ports->second.empty())
+ contexts_to_ports_.erase(context);
+ return true;
+ }
+
+ // Returns true if this tracker has any reference to |port_id|.
+ bool HasPort(int port_id) const {
+ for (auto it : contexts_to_ports_) {
+ if (it.second.count(port_id) > 0)
+ return true;
+ }
+ return false;
+ }
+
+ // Deletes all references to |port_id|.
+ void DeletePort(int port_id) {
+ for (auto it = contexts_to_ports_.begin();
+ it != contexts_to_ports_.end();) {
+ if (it->second.erase(port_id) > 0 && it->second.empty())
+ contexts_to_ports_.erase(it++);
+ else
+ ++it;
+ }
+ }
+
+ // Gets every port ID that has a reference to |context|.
+ std::set<int> GetPortsForContext(ScriptContext* context) const {
+ auto ports = contexts_to_ports_.find(context);
+ return ports == contexts_to_ports_.end() ? std::set<int>() : ports->second;
+ }
+
+ private:
+ // Maps ScriptContexts to the port IDs that have a reference to it.
+ std::map<ScriptContext*, std::set<int>> contexts_to_ports_;
+
+ DISALLOW_COPY_AND_ASSIGN(PortTracker);
+};
+
+base::LazyInstance<PortTracker> g_port_tracker = LAZY_INSTANCE_INITIALIZER;
+
+const char kPortClosedError[] = "Attempting to use a disconnected port object";
+
+class ExtensionImpl : public ObjectBackedNativeHandler {
+ public:
+ ExtensionImpl(Dispatcher* dispatcher, ScriptContext* context)
+ : ObjectBackedNativeHandler(context),
+ dispatcher_(dispatcher),
+ weak_ptr_factory_(this) {
+ RouteFunction(
+ "CloseChannel",
+ base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this)));
+ RouteFunction(
+ "PortAddRef",
+ base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this)));
+ RouteFunction(
+ "PortRelease",
+ base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this)));
+ RouteFunction(
+ "PostMessage",
+ base::Bind(&ExtensionImpl::PostMessage, base::Unretained(this)));
+ // TODO(fsamuel, kalman): Move BindToGC out of messaging natives.
+ RouteFunction("BindToGC",
+ base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this)));
+
+ // Observe |context| so that port references to it can be cleared.
+ context->AddInvalidationObserver(base::Bind(
+ &ExtensionImpl::OnContextInvalidated, weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ ~ExtensionImpl() override {}
+
+ private:
+ void OnContextInvalidated() {
+ for (int port_id : g_port_tracker.Get().GetPortsForContext(context()))
+ ReleasePort(port_id);
+ }
+
+ void ClearPortDataAndNotifyDispatcher(int port_id) {
+ g_port_tracker.Get().DeletePort(port_id);
+ dispatcher_->ClearPortData(port_id);
+ }
+
+ // Sends a message along the given channel.
+ void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (!render_frame)
+ return;
+
+ // Arguments are (int32_t port_id, string message).
+ CHECK(args.Length() == 2 && args[0]->IsInt32() && args[1]->IsString());
+
+ int port_id = args[0].As<v8::Int32>()->Value();
+ if (!g_port_tracker.Get().HasPort(port_id)) {
+ v8::Local<v8::String> error_message =
+ ToV8StringUnsafe(args.GetIsolate(), kPortClosedError);
+ args.GetIsolate()->ThrowException(v8::Exception::Error(error_message));
+ return;
+ }
+
+ render_frame->Send(new ExtensionHostMsg_PostMessage(
+ render_frame->GetRoutingID(), port_id,
+ Message(*v8::String::Utf8Value(args[1]),
+ blink::WebUserGestureIndicator::isProcessingUserGesture())));
+ }
+
+ // Forcefully disconnects a port.
+ void CloseChannel(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Arguments are (int32_t port_id, boolean notify_browser).
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsBoolean());
+
+ int port_id = args[0].As<v8::Int32>()->Value();
+ if (!g_port_tracker.Get().HasPort(port_id))
+ return;
+
+ // Send via the RenderThread because the RenderFrame might be closing.
+ bool notify_browser = args[1].As<v8::Boolean>()->Value();
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (notify_browser && render_frame) {
+ render_frame->Send(new ExtensionHostMsg_CloseMessagePort(
+ render_frame->GetRoutingID(), port_id, true));
+ }
+
+ ClearPortDataAndNotifyDispatcher(port_id);
+ }
+
+ // A new port has been created for a context. This occurs both when script
+ // opens a connection, and when a connection is opened to this script.
+ void PortAddRef(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Arguments are (int32_t port_id).
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsInt32());
+
+ int port_id = args[0].As<v8::Int32>()->Value();
+ g_port_tracker.Get().AddReference(context(), port_id);
+ }
+
+ // The frame a port lived in has been destroyed. When there are no more
+ // frames with a reference to a given port, we will disconnect it and notify
+ // the other end of the channel.
+ // TODO(robwu): Port lifetime management has moved to the browser, this is no
+ // longer needed. See .destroy_() inmessaging.js for more details.
+ void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Arguments are (int32_t port_id).
+ CHECK(args.Length() == 1 && args[0]->IsInt32());
+ ReleasePort(args[0].As<v8::Int32>()->Value());
+ }
+
+ // Releases the reference to |port_id| for this context, and clears all port
+ // data if there are no more references.
+ void ReleasePort(int port_id) {
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (g_port_tracker.Get().RemoveReference(context(), port_id) &&
+ !g_port_tracker.Get().HasPort(port_id) && render_frame) {
+ render_frame->Send(new ExtensionHostMsg_CloseMessagePort(
+ render_frame->GetRoutingID(), port_id, false));
+ }
+ }
+
+ // void BindToGC(object, callback, port_id)
+ //
+ // Binds |callback| to be invoked *sometime after* |object| is garbage
+ // collected. We don't call the method re-entrantly so as to avoid executing
+ // JS in some bizarro undefined mid-GC state, nor do we then call into the
+ // script context if it's been invalidated.
+ //
+ // If the script context *is* invalidated in the meantime, as a slight hack,
+ // release the port with ID |port_id| if it's >= 0.
+ void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 3 && args[0]->IsObject() && args[1]->IsFunction() &&
+ args[2]->IsInt32());
+ int port_id = args[2].As<v8::Int32>()->Value();
+ base::Closure fallback = base::Bind(&base::DoNothing);
+ if (port_id >= 0) {
+ fallback = base::Bind(&ExtensionImpl::ReleasePort,
+ weak_ptr_factory_.GetWeakPtr(), port_id);
+ }
+ // Destroys itself when the object is GC'd or context is invalidated.
+ new GCCallback(context(), args[0].As<v8::Object>(),
+ args[1].As<v8::Function>(), fallback);
+ }
+
+ // Dispatcher handle. Not owned.
+ Dispatcher* dispatcher_;
+
+ base::WeakPtrFactory<ExtensionImpl> weak_ptr_factory_;
+};
+
+void DispatchOnConnectToScriptContext(
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo* source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id,
+ bool* port_created,
+ ScriptContext* script_context) {
+ v8::Isolate* isolate = script_context->isolate();
+ v8::HandleScope handle_scope(isolate);
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+
+ const std::string& source_url_spec = info.source_url.spec();
+ std::string target_extension_id = script_context->GetExtensionID();
+ const Extension* extension = script_context->extension();
+
+ v8::Local<v8::Value> tab = v8::Null(isolate);
+ v8::Local<v8::Value> tls_channel_id_value = v8::Undefined(isolate);
+ v8::Local<v8::Value> guest_process_id = v8::Undefined(isolate);
+ v8::Local<v8::Value> guest_render_frame_routing_id = v8::Undefined(isolate);
+
+ if (extension) {
+ if (!source->tab.empty() && !extension->is_platform_app())
+ tab = converter->ToV8Value(&source->tab, script_context->v8_context());
+
+ ExternallyConnectableInfo* externally_connectable =
+ ExternallyConnectableInfo::Get(extension);
+ if (externally_connectable &&
+ externally_connectable->accepts_tls_channel_id) {
+ v8::Local<v8::String> v8_tls_channel_id;
+ if (ToV8String(isolate, tls_channel_id.c_str(), &v8_tls_channel_id))
+ tls_channel_id_value = v8_tls_channel_id;
+ }
+
+ if (info.guest_process_id != content::ChildProcessHost::kInvalidUniqueID) {
+ guest_process_id = v8::Integer::New(isolate, info.guest_process_id);
+ guest_render_frame_routing_id =
+ v8::Integer::New(isolate, info.guest_render_frame_routing_id);
+ }
+ }
+
+ v8::Local<v8::String> v8_channel_name;
+ v8::Local<v8::String> v8_source_id;
+ v8::Local<v8::String> v8_target_extension_id;
+ v8::Local<v8::String> v8_source_url_spec;
+ if (!ToV8String(isolate, channel_name.c_str(), &v8_channel_name) ||
+ !ToV8String(isolate, info.source_id.c_str(), &v8_source_id) ||
+ !ToV8String(isolate, target_extension_id.c_str(),
+ &v8_target_extension_id) ||
+ !ToV8String(isolate, source_url_spec.c_str(), &v8_source_url_spec)) {
+ NOTREACHED() << "dispatchOnConnect() passed non-string argument";
+ return;
+ }
+
+ v8::Local<v8::Value> arguments[] = {
+ // portId
+ v8::Integer::New(isolate, target_port_id),
+ // channelName
+ v8_channel_name,
+ // sourceTab
+ tab,
+ // source_frame_id
+ v8::Integer::New(isolate, source->frame_id),
+ // guestProcessId
+ guest_process_id,
+ // guestRenderFrameRoutingId
+ guest_render_frame_routing_id,
+ // sourceExtensionId
+ v8_source_id,
+ // targetExtensionId
+ v8_target_extension_id,
+ // sourceUrl
+ v8_source_url_spec,
+ // tlsChannelId
+ tls_channel_id_value,
+ };
+
+ v8::Local<v8::Value> retval =
+ script_context->module_system()->CallModuleMethod(
+ "messaging", "dispatchOnConnect", arraysize(arguments), arguments);
+
+ if (!IsEmptyOrUndefied(retval)) {
+ CHECK(retval->IsBoolean());
+ *port_created |= retval.As<v8::Boolean>()->Value();
+ } else {
+ LOG(ERROR) << "Empty return value from dispatchOnConnect.";
+ }
+}
+
+void DeliverMessageToScriptContext(const Message& message,
+ int target_port_id,
+ ScriptContext* script_context) {
+ v8::Isolate* isolate = script_context->isolate();
+ v8::HandleScope handle_scope(isolate);
+
+ // Check to see whether the context has this port before bothering to create
+ // the message.
+ v8::Local<v8::Value> port_id_handle =
+ v8::Integer::New(isolate, target_port_id);
+ v8::Local<v8::Value> has_port =
+ script_context->module_system()->CallModuleMethod("messaging", "hasPort",
+ 1, &port_id_handle);
+ // Could be empty/undefined if an exception was thrown.
+ // TODO(kalman): Should this be built into CallModuleMethod?
+ if (IsEmptyOrUndefied(has_port))
+ return;
+ CHECK(has_port->IsBoolean());
+ if (!has_port.As<v8::Boolean>()->Value())
+ return;
+
+ v8::Local<v8::String> v8_data;
+ if (!ToV8String(isolate, message.data.c_str(), &v8_data))
+ return;
+ std::vector<v8::Local<v8::Value>> arguments;
+ arguments.push_back(v8_data);
+ arguments.push_back(port_id_handle);
+
+ scoped_ptr<blink::WebScopedUserGesture> web_user_gesture;
+ scoped_ptr<blink::WebScopedWindowFocusAllowedIndicator> allow_window_focus;
+ if (message.user_gesture) {
+ web_user_gesture.reset(new blink::WebScopedUserGesture);
+
+ if (script_context->web_frame()) {
+ blink::WebDocument document = script_context->web_frame()->document();
+ allow_window_focus.reset(new blink::WebScopedWindowFocusAllowedIndicator(
+ &document));
+ }
+ }
+
+ script_context->module_system()->CallModuleMethod(
+ "messaging", "dispatchOnMessage", &arguments);
+}
+
+void DispatchOnDisconnectToScriptContext(int port_id,
+ const std::string& error_message,
+ ScriptContext* script_context) {
+ v8::Isolate* isolate = script_context->isolate();
+ v8::HandleScope handle_scope(isolate);
+
+ std::vector<v8::Local<v8::Value>> arguments;
+ arguments.push_back(v8::Integer::New(isolate, port_id));
+ v8::Local<v8::String> v8_error_message;
+ if (!error_message.empty())
+ ToV8String(isolate, error_message.c_str(), &v8_error_message);
+ if (!v8_error_message.IsEmpty()) {
+ arguments.push_back(v8_error_message);
+ } else {
+ arguments.push_back(v8::Null(isolate));
+ }
+
+ script_context->module_system()->CallModuleMethod(
+ "messaging", "dispatchOnDisconnect", &arguments);
+}
+
+} // namespace
+
+ObjectBackedNativeHandler* MessagingBindings::Get(Dispatcher* dispatcher,
+ ScriptContext* context) {
+ return new ExtensionImpl(dispatcher, context);
+}
+
+// static
+void MessagingBindings::DispatchOnConnect(
+ const ScriptContextSet& context_set,
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id,
+ content::RenderFrame* restrict_to_render_frame) {
+ int routing_id = restrict_to_render_frame
+ ? restrict_to_render_frame->GetRoutingID()
+ : MSG_ROUTING_NONE;
+ bool port_created = false;
+ context_set.ForEach(
+ info.target_id, restrict_to_render_frame,
+ base::Bind(&DispatchOnConnectToScriptContext, target_port_id,
+ channel_name, &source, info, tls_channel_id, &port_created));
+ // Note: |restrict_to_render_frame| may have been deleted at this point!
+
+ if (port_created) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_OpenMessagePort(routing_id, target_port_id));
+ } else {
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_CloseMessagePort(
+ routing_id, target_port_id, false));
+ }
+}
+
+// static
+void MessagingBindings::DeliverMessage(
+ const ScriptContextSet& context_set,
+ int target_port_id,
+ const Message& message,
+ content::RenderFrame* restrict_to_render_frame) {
+ context_set.ForEach(
+ restrict_to_render_frame,
+ base::Bind(&DeliverMessageToScriptContext, message, target_port_id));
+}
+
+// static
+void MessagingBindings::DispatchOnDisconnect(
+ const ScriptContextSet& context_set,
+ int port_id,
+ const std::string& error_message,
+ content::RenderFrame* restrict_to_render_frame) {
+ context_set.ForEach(
+ restrict_to_render_frame,
+ base::Bind(&DispatchOnDisconnectToScriptContext, port_id, error_message));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/messaging_bindings.h b/chromium/extensions/renderer/messaging_bindings.h
new file mode 100644
index 00000000000..a7a1c6fef80
--- /dev/null
+++ b/chromium/extensions/renderer/messaging_bindings.h
@@ -0,0 +1,73 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_MESSAGING_BINDINGS_H_
+#define EXTENSIONS_RENDERER_MESSAGING_BINDINGS_H_
+
+#include <string>
+
+#include "extensions/renderer/script_context_set.h"
+
+struct ExtensionMsg_ExternalConnectionInfo;
+struct ExtensionMsg_TabConnectionInfo;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace content {
+class RenderFrame;
+}
+
+namespace v8 {
+class Extension;
+}
+
+namespace extensions {
+class Dispatcher;
+struct Message;
+class ObjectBackedNativeHandler;
+class ScriptContextSet;
+
+// Manually implements JavaScript bindings for extension messaging.
+//
+// TODO(aa): This should all get re-implemented using SchemaGeneratedBindings.
+// If anything needs to be manual for some reason, it should be implemented in
+// its own class.
+class MessagingBindings {
+ public:
+ // Creates an instance of the extension.
+ static ObjectBackedNativeHandler* Get(Dispatcher* dispatcher,
+ ScriptContext* context);
+
+ // Dispatches the onConnect content script messaging event to some contexts
+ // in |context_set|. If |restrict_to_render_frame| is specified, only contexts
+ // in that render frame will receive the message.
+ static void DispatchOnConnect(const ScriptContextSet& context_set,
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id,
+ content::RenderFrame* restrict_to_render_frame);
+
+ // Delivers a message sent using content script messaging to some of the
+ // contexts in |bindings_context_set|. If |restrict_to_render_frame| is
+ // specified, only contexts in that render view will receive the message.
+ static void DeliverMessage(const ScriptContextSet& context_set,
+ int target_port_id,
+ const Message& message,
+ content::RenderFrame* restrict_to_render_frame);
+
+ // Dispatches the onDisconnect event in response to the channel being closed.
+ static void DispatchOnDisconnect(
+ const ScriptContextSet& context_set,
+ int port_id,
+ const std::string& error_message,
+ content::RenderFrame* restrict_to_render_frame);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_MESSAGING_BINDINGS_H_
diff --git a/chromium/extensions/renderer/messaging_utils_unittest.cc b/chromium/extensions/renderer/messaging_utils_unittest.cc
new file mode 100644
index 00000000000..fbb2dec93c8
--- /dev/null
+++ b/chromium/extensions/renderer/messaging_utils_unittest.cc
@@ -0,0 +1,196 @@
+// Copyright 2014 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.
+
+#include "base/strings/stringprintf.h"
+#include "extensions/renderer/module_system_test.h"
+#include "grit/extensions_renderer_resources.h"
+
+namespace extensions {
+namespace {
+
+class MessagingUtilsUnittest : public ModuleSystemTest {
+ protected:
+ void RegisterTestModule(const char* code) {
+ env()->RegisterModule(
+ "test",
+ base::StringPrintf(
+ "var assert = requireNative('assert');\n"
+ "var AssertTrue = assert.AssertTrue;\n"
+ "var AssertFalse = assert.AssertFalse;\n"
+ "var messagingUtils = require('messaging_utils');\n"
+ "%s",
+ code));
+ }
+
+ private:
+ void SetUp() override {
+ ModuleSystemTest::SetUp();
+
+ env()->RegisterModule("messaging_utils", IDR_MESSAGING_UTILS_JS);
+ }
+};
+
+TEST_F(MessagingUtilsUnittest, TestNothing) {
+ ExpectNoAssertionsMade();
+}
+
+TEST_F(MessagingUtilsUnittest, NoArguments) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments();\n"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, ZeroArguments) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments([]);"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, TooManyArgumentsNoOptions) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(\n"
+ " ['a', 'b', 'c', 'd']);\n"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, TooManyArgumentsWithOptions) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(\n"
+ " ['a', 'b', 'c', 'd', 'e'], true);\n"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, FinalArgumentIsNotAFunctionNoOptions) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(\n"
+ " ['a', 'b', 'c']);\n"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, FinalArgumentIsNotAFunctionWithOptions) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(\n"
+ " ['a', 'b', 'c', 'd'], true);\n"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, OneStringArgument) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ // Because the request argument is required, a single argument must get
+ // mapped to it rather than to the optional targetId argument.
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(['a']);\n"
+ "AssertTrue(args.length == 3);\n"
+ "AssertTrue(args[0] === null);\n"
+ "AssertTrue(args[1] == 'a');\n"
+ "AssertTrue(args[2] === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, OneStringAndOneNullArgument) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ // Explicitly specifying null as the request is allowed.
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(['a', null]);\n"
+ "AssertTrue(args.length == 3);\n"
+ "AssertTrue(args[0] == 'a');\n"
+ "AssertTrue(args[1] === null);\n"
+ "AssertTrue(args[2] === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, OneNullAndOneStringArgument) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments([null, 'a']);\n"
+ "AssertTrue(args.length == 3);\n"
+ "AssertTrue(args[0] === null);\n"
+ "AssertTrue(args[1] == 'a');\n"
+ "AssertTrue(args[2] === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, OneStringAndOneFunctionArgument) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ // When the arguments are a string and a function, the function is
+ // unambiguously the responseCallback. Because the request argument is
+ // required, the remaining argument must get mapped to it rather than to the
+ // optional targetId argument.
+ RegisterTestModule(
+ "var cb = function() {};\n"
+ "var args = messagingUtils.alignSendMessageArguments(['a', cb]);\n"
+ "AssertTrue(args.length == 3);\n"
+ "AssertTrue(args[0] === null);\n"
+ "AssertTrue(args[1] == 'a');\n"
+ "AssertTrue(args[2] == cb);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, OneStringAndOneObjectArgument) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ // This tests an ambiguous set of arguments when options are present:
+ // chrome.runtime.sendMessage('target', {'msg': 'this is a message'});
+ // vs.
+ // chrome.runtime.sendMessage('request', {'includeTlsChannelId': true});
+ //
+ // The question is whether the string should map to the target and the
+ // dictionary to the message, or whether the string should map to the message
+ // and the dictionary to the options. Because the target and message arguments
+ // predate the options argument, we bind the string in this case to the
+ // targetId.
+ RegisterTestModule(
+ "var obj = {'b': true};\n"
+ "var args = messagingUtils.alignSendMessageArguments(['a', obj], true);\n"
+ "AssertTrue(args.length == 4);\n"
+ "AssertTrue(args[0] == 'a');\n"
+ "AssertTrue(args[1] == obj);\n"
+ "AssertTrue(args[2] === null);\n"
+ "AssertTrue(args[3] === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, TwoObjectArguments) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ // When two non-string arguments are provided and options are present, the
+ // two arguments must match request and options, respectively, because
+ // targetId must be a string.
+ RegisterTestModule(
+ "var obj1 = {'a': 'foo'};\n"
+ "var obj2 = {'b': 'bar'};\n"
+ "var args = messagingUtils.alignSendMessageArguments(\n"
+ " [obj1, obj2], true);\n"
+ "AssertTrue(args.length == 4);\n"
+ "AssertTrue(args[0] === null);\n"
+ "AssertTrue(args[1] == obj1);\n"
+ "AssertTrue(args[2] == obj2);\n"
+ "AssertTrue(args[3] === null);");
+ env()->module_system()->Require("test");
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/module_system.cc b/chromium/extensions/renderer/module_system.cc
new file mode 100644
index 00000000000..4ff9b5c5f51
--- /dev/null
+++ b/chromium/extensions/renderer/module_system.cc
@@ -0,0 +1,757 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/module_system.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/trace_event.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/renderer/console.h"
+#include "extensions/renderer/safe_builtins.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "gin/modules/module_registry.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+namespace {
+
+const char* kModuleSystem = "module_system";
+const char* kModuleName = "module_name";
+const char* kModuleField = "module_field";
+const char* kModulesField = "modules";
+
+// Logs an error for the calling context in preparation for potentially
+// crashing the renderer, with some added metadata about the context:
+// - Its type (blessed, unblessed, etc).
+// - Whether it's valid.
+// - The extension ID, if one exists.
+// Crashing won't happen in stable/beta releases, but is encouraged to happen
+// in the less stable released to catch errors early.
+void Fatal(ScriptContext* context, const std::string& message) {
+ // Prepend some context metadata.
+ std::string full_message = "(";
+ if (!context->is_valid())
+ full_message += "Invalid ";
+ full_message += context->GetContextTypeDescription();
+ full_message += " context";
+ if (context->extension()) {
+ full_message += " for ";
+ full_message += context->extension()->id();
+ }
+ full_message += ") ";
+ full_message += message;
+
+ ExtensionsClient* client = ExtensionsClient::Get();
+ if (client->ShouldSuppressFatalErrors()) {
+ console::Error(context->GetRenderFrame(), full_message);
+ client->RecordDidSuppressFatalError();
+ } else {
+ console::Fatal(context->GetRenderFrame(), full_message);
+ }
+}
+
+void Warn(v8::Isolate* isolate, const std::string& message) {
+ ScriptContext* script_context =
+ ScriptContextSet::GetContextByV8Context(isolate->GetCurrentContext());
+ console::Warn(script_context ? script_context->GetRenderFrame() : nullptr,
+ message);
+}
+
+// Default exception handler which logs the exception.
+class DefaultExceptionHandler : public ModuleSystem::ExceptionHandler {
+ public:
+ explicit DefaultExceptionHandler(ScriptContext* context)
+ : ModuleSystem::ExceptionHandler(context) {}
+
+ // Fatally dumps the debug info from |try_catch| to the console.
+ // Make sure this is never used for exceptions that originate in external
+ // code!
+ void HandleUncaughtException(const v8::TryCatch& try_catch) override {
+ v8::HandleScope handle_scope(context_->isolate());
+ std::string stack_trace = "<stack trace unavailable>";
+ v8::Local<v8::Value> v8_stack_trace;
+ if (try_catch.StackTrace(context_->v8_context()).ToLocal(&v8_stack_trace)) {
+ v8::String::Utf8Value stack_value(v8_stack_trace);
+ if (*stack_value)
+ stack_trace.assign(*stack_value, stack_value.length());
+ else
+ stack_trace = "<could not convert stack trace to string>";
+ }
+ Fatal(context_, CreateExceptionString(try_catch) + "{" + stack_trace + "}");
+ }
+};
+
+// Sets a property on the "exports" object for bindings. Called by JS with
+// exports.$set(<key>, <value>).
+void SetExportsProperty(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Local<v8::Object> obj = args.This();
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsString());
+ v8::Maybe<bool> result =
+ obj->DefineOwnProperty(args.GetIsolate()->GetCurrentContext(),
+ args[0]->ToString(), args[1], v8::ReadOnly);
+ if (!result.FromMaybe(false))
+ LOG(ERROR) << "Failed to set private property on the export.";
+}
+
+} // namespace
+
+std::string ModuleSystem::ExceptionHandler::CreateExceptionString(
+ const v8::TryCatch& try_catch) {
+ v8::Local<v8::Message> message(try_catch.Message());
+ if (message.IsEmpty()) {
+ return "try_catch has no message";
+ }
+
+ std::string resource_name = "<unknown resource>";
+ if (!message->GetScriptOrigin().ResourceName().IsEmpty()) {
+ v8::String::Utf8Value resource_name_v8(
+ message->GetScriptOrigin().ResourceName());
+ resource_name.assign(*resource_name_v8, resource_name_v8.length());
+ }
+
+ std::string error_message = "<no error message>";
+ if (!message->Get().IsEmpty()) {
+ v8::String::Utf8Value error_message_v8(message->Get());
+ error_message.assign(*error_message_v8, error_message_v8.length());
+ }
+
+ auto maybe = message->GetLineNumber(context_->v8_context());
+ int line_number = maybe.IsJust() ? maybe.FromJust() : 0;
+ return base::StringPrintf("%s:%d: %s",
+ resource_name.c_str(),
+ line_number,
+ error_message.c_str());
+}
+
+ModuleSystem::ModuleSystem(ScriptContext* context, SourceMap* source_map)
+ : ObjectBackedNativeHandler(context),
+ context_(context),
+ source_map_(source_map),
+ natives_enabled_(0),
+ exception_handler_(new DefaultExceptionHandler(context)),
+ weak_factory_(this) {
+ RouteFunction(
+ "require",
+ base::Bind(&ModuleSystem::RequireForJs, base::Unretained(this)));
+ RouteFunction(
+ "requireNative",
+ base::Bind(&ModuleSystem::RequireNative, base::Unretained(this)));
+ RouteFunction(
+ "requireAsync",
+ base::Bind(&ModuleSystem::RequireAsync, base::Unretained(this)));
+ RouteFunction("privates",
+ base::Bind(&ModuleSystem::Private, base::Unretained(this)));
+
+ v8::Local<v8::Object> global(context->v8_context()->Global());
+ v8::Isolate* isolate = context->isolate();
+ SetPrivate(global, kModulesField, v8::Object::New(isolate));
+ SetPrivate(global, kModuleSystem, v8::External::New(isolate, this));
+
+ gin::ModuleRegistry::From(context->v8_context())->AddObserver(this);
+ if (context_->GetRenderFrame()) {
+ context_->GetRenderFrame()->EnsureMojoBuiltinsAreAvailable(
+ context->isolate(), context->v8_context());
+ }
+}
+
+ModuleSystem::~ModuleSystem() {
+}
+
+void ModuleSystem::Invalidate() {
+ // Clear the module system properties from the global context. It's polite,
+ // and we use this as a signal in lazy handlers that we no longer exist.
+ {
+ v8::HandleScope scope(GetIsolate());
+ v8::Local<v8::Object> global = context()->v8_context()->Global();
+ DeletePrivate(global, kModulesField);
+ DeletePrivate(global, kModuleSystem);
+ }
+
+ // Invalidate all active and clobbered NativeHandlers we own.
+ for (const auto& handler : native_handler_map_)
+ handler.second->Invalidate();
+ for (const auto& clobbered_handler : clobbered_native_handlers_)
+ clobbered_handler->Invalidate();
+
+ ObjectBackedNativeHandler::Invalidate();
+}
+
+ModuleSystem::NativesEnabledScope::NativesEnabledScope(
+ ModuleSystem* module_system)
+ : module_system_(module_system) {
+ module_system_->natives_enabled_++;
+}
+
+ModuleSystem::NativesEnabledScope::~NativesEnabledScope() {
+ module_system_->natives_enabled_--;
+ CHECK_GE(module_system_->natives_enabled_, 0);
+}
+
+void ModuleSystem::HandleException(const v8::TryCatch& try_catch) {
+ exception_handler_->HandleUncaughtException(try_catch);
+}
+
+v8::MaybeLocal<v8::Object> ModuleSystem::Require(
+ const std::string& module_name) {
+ v8::Local<v8::String> v8_module_name;
+ if (!ToV8String(GetIsolate(), module_name, &v8_module_name))
+ return v8::MaybeLocal<v8::Object>();
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Value> value = RequireForJsInner(
+ v8_module_name);
+ if (value.IsEmpty() || !value->IsObject())
+ return v8::MaybeLocal<v8::Object>();
+ return handle_scope.Escape(value.As<v8::Object>());
+}
+
+void ModuleSystem::RequireForJs(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (!args[0]->IsString()) {
+ NOTREACHED() << "require() called with a non-string argument";
+ return;
+ }
+ v8::Local<v8::String> module_name = args[0].As<v8::String>();
+ args.GetReturnValue().Set(RequireForJsInner(module_name));
+}
+
+v8::Local<v8::Value> ModuleSystem::RequireForJsInner(
+ v8::Local<v8::String> module_name) {
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Context> v8_context = context()->v8_context();
+ v8::Context::Scope context_scope(v8_context);
+
+ v8::Local<v8::Object> global(context()->v8_context()->Global());
+
+ // The module system might have been deleted. This can happen if a different
+ // context keeps a reference to us, but our frame is destroyed (e.g.
+ // background page keeps reference to chrome object in a closed popup).
+ v8::Local<v8::Value> modules_value;
+ if (!GetPrivate(global, kModulesField, &modules_value) ||
+ modules_value->IsUndefined()) {
+ Warn(GetIsolate(), "Extension view no longer exists");
+ return v8::Undefined(GetIsolate());
+ }
+
+ v8::Local<v8::Object> modules(v8::Local<v8::Object>::Cast(modules_value));
+ v8::Local<v8::Value> exports;
+ if (!GetPrivateProperty(v8_context, modules, module_name, &exports) ||
+ !exports->IsUndefined())
+ return handle_scope.Escape(exports);
+
+ exports = LoadModule(*v8::String::Utf8Value(module_name));
+ SetPrivateProperty(v8_context, modules, module_name, exports);
+ return handle_scope.Escape(exports);
+}
+
+v8::Local<v8::Value> ModuleSystem::CallModuleMethod(
+ const std::string& module_name,
+ const std::string& method_name) {
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Value> no_args;
+ return handle_scope.Escape(
+ CallModuleMethod(module_name, method_name, 0, &no_args));
+}
+
+v8::Local<v8::Value> ModuleSystem::CallModuleMethod(
+ const std::string& module_name,
+ const std::string& method_name,
+ std::vector<v8::Local<v8::Value>>* args) {
+ return CallModuleMethod(module_name, method_name, args->size(), args->data());
+}
+
+v8::Local<v8::Value> ModuleSystem::CallModuleMethod(
+ const std::string& module_name,
+ const std::string& method_name,
+ int argc,
+ v8::Local<v8::Value> argv[]) {
+ TRACE_EVENT2("v8",
+ "v8.callModuleMethod",
+ "module_name",
+ module_name,
+ "method_name",
+ method_name);
+
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Context> v8_context = context()->v8_context();
+ v8::Context::Scope context_scope(v8_context);
+
+ v8::Local<v8::String> v8_module_name;
+ v8::Local<v8::String> v8_method_name;
+ if (!ToV8String(GetIsolate(), module_name.c_str(), &v8_module_name) ||
+ !ToV8String(GetIsolate(), method_name.c_str(), &v8_method_name)) {
+ return handle_scope.Escape(v8::Undefined(GetIsolate()));
+ }
+
+ v8::Local<v8::Value> module;
+ {
+ NativesEnabledScope natives_enabled(this);
+ module = RequireForJsInner(v8_module_name);
+ }
+
+ if (module.IsEmpty() || !module->IsObject()) {
+ Fatal(context_,
+ "Failed to get module " + module_name + " to call " + method_name);
+ return handle_scope.Escape(v8::Undefined(GetIsolate()));
+ }
+
+ v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(module);
+ v8::Local<v8::Value> value;
+ if (!GetProperty(v8_context, object, v8_method_name, &value) ||
+ !value->IsFunction()) {
+ Fatal(context_, module_name + "." + method_name + " is not a function");
+ return handle_scope.Escape(v8::Undefined(GetIsolate()));
+ }
+
+ v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(value);
+ v8::Local<v8::Value> result;
+ {
+ v8::TryCatch try_catch(GetIsolate());
+ try_catch.SetCaptureMessage(true);
+ result = context_->CallFunction(func, argc, argv);
+ if (try_catch.HasCaught()) {
+ HandleException(try_catch);
+ result = v8::Undefined(GetIsolate());
+ }
+ }
+ return handle_scope.Escape(result);
+}
+
+void ModuleSystem::RegisterNativeHandler(
+ const std::string& name,
+ scoped_ptr<NativeHandler> native_handler) {
+ ClobberExistingNativeHandler(name);
+ native_handler_map_[name] = std::move(native_handler);
+}
+
+void ModuleSystem::OverrideNativeHandlerForTest(const std::string& name) {
+ ClobberExistingNativeHandler(name);
+ overridden_native_handlers_.insert(name);
+}
+
+void ModuleSystem::RunString(const std::string& code, const std::string& name) {
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Local<v8::String> v8_code;
+ v8::Local<v8::String> v8_name;
+ if (!ToV8String(GetIsolate(), code.c_str(), &v8_code) ||
+ !ToV8String(GetIsolate(), name.c_str(), &v8_name)) {
+ Warn(GetIsolate(), "Too long code or name.");
+ return;
+ }
+ RunString(v8_code, v8_name);
+}
+
+// static
+void ModuleSystem::NativeLazyFieldGetter(
+ v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ LazyFieldGetterInner(property.As<v8::String>(), info,
+ &ModuleSystem::RequireNativeFromString);
+}
+
+// static
+void ModuleSystem::LazyFieldGetter(
+ v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ LazyFieldGetterInner(property.As<v8::String>(), info, &ModuleSystem::Require);
+}
+
+// static
+void ModuleSystem::LazyFieldGetterInner(
+ v8::Local<v8::String> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info,
+ RequireFunction require_function) {
+ CHECK(!info.Data().IsEmpty());
+ CHECK(info.Data()->IsObject());
+ v8::Isolate* isolate = info.GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Object> parameters = v8::Local<v8::Object>::Cast(info.Data());
+ // This context should be the same as context()->v8_context().
+ v8::Local<v8::Context> context = parameters->CreationContext();
+ v8::Local<v8::Object> global(context->Global());
+ v8::Local<v8::Value> module_system_value;
+ if (!GetPrivate(context, global, kModuleSystem, &module_system_value) ||
+ !module_system_value->IsExternal()) {
+ // ModuleSystem has been deleted.
+ // TODO(kalman): See comment in header file.
+ Warn(isolate,
+ "Module system has been deleted, does extension view exist?");
+ return;
+ }
+
+ ModuleSystem* module_system = static_cast<ModuleSystem*>(
+ v8::Local<v8::External>::Cast(module_system_value)->Value());
+
+ v8::Local<v8::Value> v8_module_name;
+ if (!GetPrivateProperty(context, parameters, kModuleName, &v8_module_name)) {
+ Warn(isolate, "Cannot find module.");
+ return;
+ }
+ std::string name = *v8::String::Utf8Value(v8_module_name);
+
+ // Switch to our v8 context because we need functions created while running
+ // the require()d module to belong to our context, not the current one.
+ v8::Context::Scope context_scope(context);
+ NativesEnabledScope natives_enabled_scope(module_system);
+
+ v8::TryCatch try_catch(isolate);
+ v8::Local<v8::Value> module_value;
+ if (!(module_system->*require_function)(name).ToLocal(&module_value)) {
+ module_system->HandleException(try_catch);
+ return;
+ }
+
+ v8::Local<v8::Object> module = v8::Local<v8::Object>::Cast(module_value);
+ v8::Local<v8::Value> field_value;
+ if (!GetPrivateProperty(context, parameters, kModuleField, &field_value)) {
+ module_system->HandleException(try_catch);
+ return;
+ }
+ v8::Local<v8::String> field;
+ if (!field_value->ToString(context).ToLocal(&field)) {
+ module_system->HandleException(try_catch);
+ return;
+ }
+
+ if (!IsTrue(module->Has(context, field))) {
+ std::string field_str = *v8::String::Utf8Value(field);
+ Fatal(module_system->context_,
+ "Lazy require of " + name + "." + field_str + " did not set the " +
+ field_str + " field");
+ return;
+ }
+
+ v8::Local<v8::Value> new_field;
+ if (!GetProperty(context, module, field, &new_field)) {
+ module_system->HandleException(try_catch);
+ return;
+ }
+
+ // Ok for it to be undefined, among other things it's how bindings signify
+ // that the extension doesn't have permission to use them.
+ CHECK(!new_field.IsEmpty());
+
+ // Delete the getter and set this field to |new_field| so the same object is
+ // returned every time a certain API is accessed.
+ v8::Local<v8::Value> val = info.This();
+ if (val->IsObject()) {
+ v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(val);
+ object->Delete(context, property);
+ SetProperty(context, object, property, new_field);
+ } else {
+ NOTREACHED();
+ }
+ info.GetReturnValue().Set(new_field);
+}
+
+void ModuleSystem::SetLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field) {
+ SetLazyField(
+ object, field, module_name, module_field, &ModuleSystem::LazyFieldGetter);
+}
+
+void ModuleSystem::SetLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field,
+ v8::AccessorNameGetterCallback getter) {
+ CHECK(field.size() < v8::String::kMaxLength);
+ CHECK(module_name.size() < v8::String::kMaxLength);
+ CHECK(module_field.size() < v8::String::kMaxLength);
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Object> parameters = v8::Object::New(GetIsolate());
+ v8::Local<v8::Context> context = context_->v8_context();
+ SetPrivateProperty(context, parameters, kModuleName,
+ ToV8StringUnsafe(GetIsolate(), module_name.c_str()));
+ SetPrivateProperty(context, parameters, kModuleField,
+ ToV8StringUnsafe(GetIsolate(), module_field.c_str()));
+ auto maybe = object->SetAccessor(
+ context, ToV8StringUnsafe(GetIsolate(), field.c_str()), getter, NULL,
+ parameters);
+ CHECK(IsTrue(maybe));
+}
+
+void ModuleSystem::SetNativeLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field) {
+ SetLazyField(object,
+ field,
+ module_name,
+ module_field,
+ &ModuleSystem::NativeLazyFieldGetter);
+}
+
+v8::Local<v8::Value> ModuleSystem::RunString(v8::Local<v8::String> code,
+ v8::Local<v8::String> name) {
+ return context_->RunScript(
+ name, code, base::Bind(&ExceptionHandler::HandleUncaughtException,
+ base::Unretained(exception_handler_.get())));
+}
+
+v8::Local<v8::Value> ModuleSystem::GetSource(const std::string& module_name) {
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ if (!source_map_->Contains(module_name))
+ return v8::Undefined(GetIsolate());
+ return handle_scope.Escape(
+ v8::Local<v8::Value>(source_map_->GetSource(GetIsolate(), module_name)));
+}
+
+void ModuleSystem::RequireNative(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ std::string native_name = *v8::String::Utf8Value(args[0]);
+ v8::Local<v8::Object> object;
+ if (RequireNativeFromString(native_name).ToLocal(&object))
+ args.GetReturnValue().Set(object);
+}
+
+v8::MaybeLocal<v8::Object> ModuleSystem::RequireNativeFromString(
+ const std::string& native_name) {
+ if (natives_enabled_ == 0) {
+ // HACK: if in test throw exception so that we can test the natives-disabled
+ // logic; however, under normal circumstances, this is programmer error so
+ // we could crash.
+ if (exception_handler_) {
+ GetIsolate()->ThrowException(
+ ToV8StringUnsafe(GetIsolate(), "Natives disabled"));
+ return v8::MaybeLocal<v8::Object>();
+ }
+ Fatal(context_, "Natives disabled for requireNative(" + native_name + ")");
+ return v8::MaybeLocal<v8::Object>();
+ }
+
+ if (overridden_native_handlers_.count(native_name) > 0u) {
+ v8::Local<v8::Value> value = RequireForJsInner(
+ ToV8StringUnsafe(GetIsolate(), native_name.c_str()));
+ if (value.IsEmpty() || !value->IsObject())
+ return v8::MaybeLocal<v8::Object>();
+ return value.As<v8::Object>();
+ }
+
+ NativeHandlerMap::iterator i = native_handler_map_.find(native_name);
+ if (i == native_handler_map_.end()) {
+ Fatal(context_,
+ "Couldn't find native for requireNative(" + native_name + ")");
+ return v8::MaybeLocal<v8::Object>();
+ }
+ return i->second->NewInstance();
+}
+
+void ModuleSystem::RequireAsync(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ std::string module_name = *v8::String::Utf8Value(args[0]);
+ v8::Local<v8::Context> v8_context = context_->v8_context();
+ v8::Local<v8::Promise::Resolver> resolver(
+ v8::Promise::Resolver::New(v8_context).ToLocalChecked());
+ args.GetReturnValue().Set(resolver->GetPromise());
+ scoped_ptr<v8::Global<v8::Promise::Resolver>> global_resolver(
+ new v8::Global<v8::Promise::Resolver>(GetIsolate(), resolver));
+ gin::ModuleRegistry* module_registry =
+ gin::ModuleRegistry::From(v8_context);
+ if (!module_registry) {
+ Warn(GetIsolate(), "Extension view no longer exists");
+ resolver->Reject(v8_context, v8::Exception::Error(ToV8StringUnsafe(
+ GetIsolate(), "Extension view no longer exists")));
+ return;
+ }
+ module_registry->LoadModule(
+ GetIsolate(), module_name,
+ base::Bind(&ModuleSystem::OnModuleLoaded, weak_factory_.GetWeakPtr(),
+ base::Passed(&global_resolver)));
+ if (module_registry->available_modules().count(module_name) == 0)
+ LoadModule(module_name);
+}
+
+v8::Local<v8::String> ModuleSystem::WrapSource(v8::Local<v8::String> source) {
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ // Keep in order with the arguments in RequireForJsInner.
+ v8::Local<v8::String> left = ToV8StringUnsafe(
+ GetIsolate(),
+ "(function(define, require, requireNative, requireAsync, exports, "
+ "console, privates,"
+ "$Array, $Function, $JSON, $Object, $RegExp, $String, $Error) {"
+ "'use strict';");
+ v8::Local<v8::String> right = ToV8StringUnsafe(GetIsolate(), "\n})");
+ return handle_scope.Escape(v8::Local<v8::String>(
+ v8::String::Concat(left, v8::String::Concat(source, right))));
+}
+
+void ModuleSystem::Private(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ if (!args[0]->IsObject() || args[0]->IsNull()) {
+ GetIsolate()->ThrowException(
+ v8::Exception::TypeError(ToV8StringUnsafe(GetIsolate(),
+ args[0]->IsUndefined()
+ ? "Method called without a valid receiver (this). "
+ "Did you forget to call .bind()?"
+ : "Invalid invocation: receiver is not an object!")));
+ return;
+ }
+ v8::Local<v8::Object> obj = args[0].As<v8::Object>();
+ v8::Local<v8::Value> privates;
+ if (!GetPrivate(obj, "privates", &privates) || !privates->IsObject()) {
+ privates = v8::Object::New(args.GetIsolate());
+ if (privates.IsEmpty()) {
+ GetIsolate()->ThrowException(
+ ToV8StringUnsafe(GetIsolate(), "Failed to create privates"));
+ return;
+ }
+ v8::Maybe<bool> maybe =
+ privates.As<v8::Object>()->SetPrototype(context()->v8_context(),
+ v8::Null(args.GetIsolate()));
+ CHECK(maybe.IsJust() && maybe.FromJust());
+ SetPrivate(obj, "privates", privates);
+ }
+ args.GetReturnValue().Set(privates);
+}
+
+v8::Local<v8::Value> ModuleSystem::LoadModule(const std::string& module_name) {
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Context> v8_context = context()->v8_context();
+ v8::Context::Scope context_scope(v8_context);
+
+ v8::Local<v8::Value> source(GetSource(module_name));
+ if (source.IsEmpty() || source->IsUndefined()) {
+ Fatal(context_, "No source for require(" + module_name + ")");
+ return v8::Undefined(GetIsolate());
+ }
+ v8::Local<v8::String> wrapped_source(
+ WrapSource(v8::Local<v8::String>::Cast(source)));
+ v8::Local<v8::String> v8_module_name;
+ if (!ToV8String(GetIsolate(), module_name.c_str(), &v8_module_name)) {
+ NOTREACHED() << "module_name is too long";
+ return v8::Undefined(GetIsolate());
+ }
+ // Modules are wrapped in (function(){...}) so they always return functions.
+ v8::Local<v8::Value> func_as_value =
+ RunString(wrapped_source, v8_module_name);
+ if (func_as_value.IsEmpty() || func_as_value->IsUndefined()) {
+ Fatal(context_, "Bad source for require(" + module_name + ")");
+ return v8::Undefined(GetIsolate());
+ }
+
+ v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(func_as_value);
+
+ v8::Local<v8::Object> define_object = v8::Object::New(GetIsolate());
+ gin::ModuleRegistry::InstallGlobals(GetIsolate(), define_object);
+
+ v8::Local<v8::Object> exports = v8::Object::New(GetIsolate());
+
+ v8::Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(
+ GetIsolate(),
+ &SetExportsProperty);
+ v8::Local<v8::String> v8_key;
+ if (!v8_helpers::ToV8String(GetIsolate(), "$set", &v8_key)) {
+ NOTREACHED();
+ return v8::Undefined(GetIsolate());
+ }
+
+ v8::Local<v8::Function> function;
+ if (!tmpl->GetFunction(v8_context).ToLocal(&function)) {
+ NOTREACHED();
+ return v8::Undefined(GetIsolate());
+ }
+
+ exports->DefineOwnProperty(v8_context, v8_key, function, v8::ReadOnly)
+ .FromJust();
+
+ v8::Local<v8::Object> natives(NewInstance());
+ CHECK(!natives.IsEmpty()); // this can fail if v8 has issues
+
+ // These must match the argument order in WrapSource.
+ v8::Local<v8::Value> args[] = {
+ // AMD.
+ GetPropertyUnsafe(v8_context, define_object, "define"),
+ // CommonJS.
+ GetPropertyUnsafe(v8_context, natives, "require",
+ v8::NewStringType::kInternalized),
+ GetPropertyUnsafe(v8_context, natives, "requireNative",
+ v8::NewStringType::kInternalized),
+ GetPropertyUnsafe(v8_context, natives, "requireAsync",
+ v8::NewStringType::kInternalized),
+ exports,
+ // Libraries that we magically expose to every module.
+ console::AsV8Object(GetIsolate()),
+ GetPropertyUnsafe(v8_context, natives, "privates",
+ v8::NewStringType::kInternalized),
+ // Each safe builtin. Keep in order with the arguments in WrapSource.
+ context_->safe_builtins()->GetArray(),
+ context_->safe_builtins()->GetFunction(),
+ context_->safe_builtins()->GetJSON(),
+ context_->safe_builtins()->GetObjekt(),
+ context_->safe_builtins()->GetRegExp(),
+ context_->safe_builtins()->GetString(),
+ context_->safe_builtins()->GetError(),
+ };
+ {
+ v8::TryCatch try_catch(GetIsolate());
+ try_catch.SetCaptureMessage(true);
+ context_->CallFunction(func, arraysize(args), args);
+ if (try_catch.HasCaught()) {
+ HandleException(try_catch);
+ return v8::Undefined(GetIsolate());
+ }
+ }
+ return handle_scope.Escape(exports);
+}
+
+void ModuleSystem::OnDidAddPendingModule(
+ const std::string& id,
+ const std::vector<std::string>& dependencies) {
+ bool module_system_managed = source_map_->Contains(id);
+
+ gin::ModuleRegistry* registry =
+ gin::ModuleRegistry::From(context_->v8_context());
+ DCHECK(registry);
+ for (const auto& dependency : dependencies) {
+ // If a dependency is not available, and either the module or this
+ // dependency is managed by ModuleSystem, attempt to load it. Other
+ // gin::ModuleRegistry users (WebUI and users of the mojoPrivate API) are
+ // responsible for loading their module dependencies when required.
+ if (registry->available_modules().count(dependency) == 0 &&
+ (module_system_managed || source_map_->Contains(dependency))) {
+ LoadModule(dependency);
+ }
+ }
+ registry->AttemptToLoadMoreModules(GetIsolate());
+}
+
+void ModuleSystem::OnModuleLoaded(
+ scoped_ptr<v8::Global<v8::Promise::Resolver>> resolver,
+ v8::Local<v8::Value> value) {
+ if (!is_valid())
+ return;
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Promise::Resolver> resolver_local(
+ v8::Local<v8::Promise::Resolver>::New(GetIsolate(), *resolver));
+ resolver_local->Resolve(context()->v8_context(), value);
+}
+
+void ModuleSystem::ClobberExistingNativeHandler(const std::string& name) {
+ NativeHandlerMap::iterator existing_handler = native_handler_map_.find(name);
+ if (existing_handler != native_handler_map_.end()) {
+ clobbered_native_handlers_.push_back(std::move(existing_handler->second));
+ native_handler_map_.erase(existing_handler);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/module_system.h b/chromium/extensions/renderer/module_system.h
new file mode 100644
index 00000000000..134d2baa05f
--- /dev/null
+++ b/chromium/extensions/renderer/module_system.h
@@ -0,0 +1,253 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_MODULE_SYSTEM_H_
+#define EXTENSIONS_RENDERER_MODULE_SYSTEM_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/renderer/native_handler.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "gin/modules/module_registry_observer.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+class ScriptContext;
+
+// A module system for JS similar to node.js' require() function.
+// Each module has three variables in the global scope:
+// - exports, an object returned to dependencies who require() this
+// module.
+// - require, a function that takes a module name as an argument and returns
+// that module's exports object.
+// - requireNative, a function that takes the name of a registered
+// NativeHandler and returns an object that contains the functions the
+// NativeHandler defines.
+//
+// Each module in a ModuleSystem is executed at most once and its exports
+// object cached.
+//
+// Note that a ModuleSystem must be used only in conjunction with a single
+// v8::Context.
+// TODO(koz): Rename this to JavaScriptModuleSystem.
+class ModuleSystem : public ObjectBackedNativeHandler,
+ public gin::ModuleRegistryObserver {
+ public:
+ class SourceMap {
+ public:
+ virtual ~SourceMap() {}
+ virtual v8::Local<v8::Value> GetSource(v8::Isolate* isolate,
+ const std::string& name) = 0;
+ virtual bool Contains(const std::string& name) = 0;
+ };
+
+ class ExceptionHandler {
+ public:
+ explicit ExceptionHandler(ScriptContext* context) : context_(context) {}
+ virtual ~ExceptionHandler() {}
+ virtual void HandleUncaughtException(const v8::TryCatch& try_catch) = 0;
+
+ protected:
+ // Formats |try_catch| as a nice string.
+ std::string CreateExceptionString(const v8::TryCatch& try_catch);
+ // A script context associated with this handler. Owned by the module
+ // system.
+ ScriptContext* context_;
+ };
+
+ // Enables native bindings for the duration of its lifetime.
+ class NativesEnabledScope {
+ public:
+ explicit NativesEnabledScope(ModuleSystem* module_system);
+ ~NativesEnabledScope();
+
+ private:
+ ModuleSystem* module_system_;
+ DISALLOW_COPY_AND_ASSIGN(NativesEnabledScope);
+ };
+
+ // |source_map| is a weak pointer.
+ ModuleSystem(ScriptContext* context, SourceMap* source_map);
+ ~ModuleSystem() override;
+
+ // Require the specified module. This is the equivalent of calling
+ // require('module_name') from the loaded JS files.
+ v8::MaybeLocal<v8::Object> Require(const std::string& module_name);
+ void Require(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Run |code| in the current context with the name |name| used for stack
+ // traces.
+ v8::Local<v8::Value> RunString(v8::Local<v8::String> code,
+ v8::Local<v8::String> name);
+
+ // Calls the specified method exported by the specified module. This is
+ // equivalent to calling require('module_name').method_name() from JS.
+ v8::Local<v8::Value> CallModuleMethod(const std::string& module_name,
+ const std::string& method_name);
+ v8::Local<v8::Value> CallModuleMethod(
+ const std::string& module_name,
+ const std::string& method_name,
+ std::vector<v8::Local<v8::Value>>* args);
+ v8::Local<v8::Value> CallModuleMethod(const std::string& module_name,
+ const std::string& method_name,
+ int argc,
+ v8::Local<v8::Value> argv[]);
+
+ // Register |native_handler| as a potential target for requireNative(), so
+ // calls to requireNative(|name|) from JS will return a new object created by
+ // |native_handler|.
+ void RegisterNativeHandler(const std::string& name,
+ scoped_ptr<NativeHandler> native_handler);
+
+ // Causes requireNative(|name|) to look for its module in |source_map_|
+ // instead of using a registered native handler. This can be used in unit
+ // tests to mock out native modules.
+ void OverrideNativeHandlerForTest(const std::string& name);
+
+ // Executes |code| in the current context with |name| as the filename.
+ void RunString(const std::string& code, const std::string& name);
+
+ // Make |object|.|field| lazily evaluate to the result of
+ // require(|module_name|)[|module_field|].
+ //
+ // TODO(kalman): All targets for this method are ObjectBackedNativeHandlers,
+ // move this logic into those classes (in fact, the chrome
+ // object is the only client, only that needs to implement it).
+ void SetLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field);
+
+ void SetLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field,
+ v8::AccessorNameGetterCallback getter);
+
+ // Make |object|.|field| lazily evaluate to the result of
+ // requireNative(|module_name|)[|module_field|].
+ // TODO(kalman): Same as above.
+ void SetNativeLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field);
+
+ // Passes exceptions to |handler| rather than console::Fatal.
+ void SetExceptionHandlerForTest(scoped_ptr<ExceptionHandler> handler) {
+ exception_handler_ = std::move(handler);
+ }
+
+ protected:
+ friend class ModuleSystemTestEnvironment;
+ friend class ScriptContext;
+ void Invalidate() override;
+
+ private:
+ typedef std::map<std::string, scoped_ptr<NativeHandler>> NativeHandlerMap;
+
+ // Retrieves the lazily defined field specified by |property|.
+ static void LazyFieldGetter(v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info);
+ // Retrieves the lazily defined field specified by |property| on a native
+ // object.
+ static void NativeLazyFieldGetter(
+ v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info);
+
+ // Called when an exception is thrown but not caught.
+ void HandleException(const v8::TryCatch& try_catch);
+
+ void RequireForJs(const v8::FunctionCallbackInfo<v8::Value>& args);
+ v8::Local<v8::Value> RequireForJsInner(v8::Local<v8::String> module_name);
+
+ typedef v8::MaybeLocal<v8::Object>(ModuleSystem::*RequireFunction)(
+ const std::string&);
+ // Base implementation of a LazyFieldGetter which uses |require_fn| to require
+ // modules.
+ static void LazyFieldGetterInner(
+ v8::Local<v8::String> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info,
+ RequireFunction require_function);
+
+ // Return the named source file stored in the source map.
+ // |args[0]| - the name of a source file in source_map_.
+ v8::Local<v8::Value> GetSource(const std::string& module_name);
+
+ // Return an object that contains the native methods defined by the named
+ // NativeHandler.
+ // |args[0]| - the name of a native handler object.
+ v8::MaybeLocal<v8::Object> RequireNativeFromString(
+ const std::string& native_name);
+ void RequireNative(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Return a promise for a requested module.
+ // |args[0]| - the name of a module.
+ void RequireAsync(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Wraps |source| in a (function(define, require, requireNative, ...) {...}).
+ v8::Local<v8::String> WrapSource(v8::Local<v8::String> source);
+
+ // NativeHandler implementation which returns the private area of an Object.
+ void Private(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Loads and runs a Javascript module.
+ v8::Local<v8::Value> LoadModule(const std::string& module_name);
+
+ // Invoked when a module is loaded in response to a requireAsync call.
+ // Resolves |resolver| with |value|.
+ void OnModuleLoaded(scoped_ptr<v8::Global<v8::Promise::Resolver>> resolver,
+ v8::Local<v8::Value> value);
+
+ // gin::ModuleRegistryObserver overrides.
+ void OnDidAddPendingModule(
+ const std::string& id,
+ const std::vector<std::string>& dependencies) override;
+
+ // Marks any existing NativeHandler named |name| as clobbered.
+ // See |clobbered_native_handlers_|.
+ void ClobberExistingNativeHandler(const std::string& name);
+
+ ScriptContext* context_;
+
+ // A map from module names to the JS source for that module. GetSource()
+ // performs a lookup on this map.
+ SourceMap* source_map_;
+
+ // A map from native handler names to native handlers.
+ NativeHandlerMap native_handler_map_;
+
+ // When 0, natives are disabled, otherwise indicates how many callers have
+ // pinned natives as enabled.
+ int natives_enabled_;
+
+ // Called when an exception is thrown but not caught in JS. Overridable by
+ // tests.
+ scoped_ptr<ExceptionHandler> exception_handler_;
+
+ // A set of native handlers that should actually be require()d as non-native
+ // handlers. This is used for tests to mock out native handlers in JS.
+ std::set<std::string> overridden_native_handlers_;
+
+ // A list of NativeHandlers that have been clobbered, either due to
+ // registering a NativeHandler when one was already registered with the same
+ // name, or due to OverrideNativeHandlerForTest. This is needed so that they
+ // can be later Invalidated. It should only happen in tests.
+ std::vector<scoped_ptr<NativeHandler>> clobbered_native_handlers_;
+
+ base::WeakPtrFactory<ModuleSystem> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ModuleSystem);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_MODULE_SYSTEM_H_
diff --git a/chromium/extensions/renderer/module_system_test.cc b/chromium/extensions/renderer/module_system_test.cc
new file mode 100644
index 00000000000..3386ff010c1
--- /dev/null
+++ b/chromium/extensions/renderer/module_system_test.cc
@@ -0,0 +1,261 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/module_system_test.h"
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/strings/string_piece.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/renderer/logging_native_handler.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "extensions/renderer/safe_builtins.h"
+#include "extensions/renderer/utils_native_handler.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace extensions {
+namespace {
+
+class FailsOnException : public ModuleSystem::ExceptionHandler {
+ public:
+ FailsOnException() : ModuleSystem::ExceptionHandler(nullptr) {}
+ void HandleUncaughtException(const v8::TryCatch& try_catch) override {
+ FAIL() << "Uncaught exception: " << CreateExceptionString(try_catch);
+ }
+};
+
+class V8ExtensionConfigurator {
+ public:
+ V8ExtensionConfigurator()
+ : safe_builtins_(SafeBuiltins::CreateV8Extension()),
+ names_(1, safe_builtins_->name()),
+ configuration_(
+ new v8::ExtensionConfiguration(static_cast<int>(names_.size()),
+ names_.data())) {
+ v8::RegisterExtension(safe_builtins_.get());
+ }
+
+ v8::ExtensionConfiguration* GetConfiguration() {
+ return configuration_.get();
+ }
+
+ private:
+ scoped_ptr<v8::Extension> safe_builtins_;
+ std::vector<const char*> names_;
+ scoped_ptr<v8::ExtensionConfiguration> configuration_;
+};
+
+base::LazyInstance<V8ExtensionConfigurator>::Leaky g_v8_extension_configurator =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// Native JS functions for doing asserts.
+class ModuleSystemTestEnvironment::AssertNatives
+ : public ObjectBackedNativeHandler {
+ public:
+ explicit AssertNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context),
+ assertion_made_(false),
+ failed_(false) {
+ RouteFunction(
+ "AssertTrue",
+ base::Bind(&AssertNatives::AssertTrue, base::Unretained(this)));
+ RouteFunction(
+ "AssertFalse",
+ base::Bind(&AssertNatives::AssertFalse, base::Unretained(this)));
+ }
+
+ bool assertion_made() { return assertion_made_; }
+ bool failed() { return failed_; }
+
+ void AssertTrue(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ assertion_made_ = true;
+ failed_ = failed_ || !args[0]->ToBoolean(args.GetIsolate())->Value();
+ }
+
+ void AssertFalse(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ assertion_made_ = true;
+ failed_ = failed_ || args[0]->ToBoolean(args.GetIsolate())->Value();
+ }
+
+ private:
+ bool assertion_made_;
+ bool failed_;
+};
+
+// Source map that operates on std::strings.
+class ModuleSystemTestEnvironment::StringSourceMap
+ : public ModuleSystem::SourceMap {
+ public:
+ StringSourceMap() {}
+ ~StringSourceMap() override {}
+
+ v8::Local<v8::Value> GetSource(v8::Isolate* isolate,
+ const std::string& name) override {
+ if (source_map_.count(name) == 0)
+ return v8::Undefined(isolate);
+ return v8::String::NewFromUtf8(isolate, source_map_[name].c_str());
+ }
+
+ bool Contains(const std::string& name) override {
+ return source_map_.count(name);
+ }
+
+ void RegisterModule(const std::string& name, const std::string& source) {
+ CHECK_EQ(0u, source_map_.count(name)) << "Module " << name << " not found";
+ source_map_[name] = source;
+ }
+
+ private:
+ std::map<std::string, std::string> source_map_;
+};
+
+ModuleSystemTestEnvironment::ModuleSystemTestEnvironment(v8::Isolate* isolate)
+ : isolate_(isolate),
+ context_holder_(new gin::ContextHolder(isolate_)),
+ handle_scope_(isolate_),
+ source_map_(new StringSourceMap()) {
+ context_holder_->SetContext(v8::Context::New(
+ isolate, g_v8_extension_configurator.Get().GetConfiguration()));
+ context_.reset(new ScriptContext(context_holder_->context(),
+ nullptr, // WebFrame
+ nullptr, // Extension
+ Feature::BLESSED_EXTENSION_CONTEXT,
+ nullptr, // Effective Extension
+ Feature::BLESSED_EXTENSION_CONTEXT));
+ context_->v8_context()->Enter();
+ assert_natives_ = new AssertNatives(context_.get());
+
+ {
+ scoped_ptr<ModuleSystem> module_system(
+ new ModuleSystem(context_.get(), source_map_.get()));
+ context_->set_module_system(std::move(module_system));
+ }
+ ModuleSystem* module_system = context_->module_system();
+ module_system->RegisterNativeHandler(
+ "assert", scoped_ptr<NativeHandler>(assert_natives_));
+ module_system->RegisterNativeHandler(
+ "logging",
+ scoped_ptr<NativeHandler>(new LoggingNativeHandler(context_.get())));
+ module_system->RegisterNativeHandler(
+ "utils",
+ scoped_ptr<NativeHandler>(new UtilsNativeHandler(context_.get())));
+ module_system->SetExceptionHandlerForTest(
+ scoped_ptr<ModuleSystem::ExceptionHandler>(new FailsOnException));
+}
+
+ModuleSystemTestEnvironment::~ModuleSystemTestEnvironment() {
+ if (context_->is_valid())
+ ShutdownModuleSystem();
+}
+
+void ModuleSystemTestEnvironment::RegisterModule(const std::string& name,
+ const std::string& code) {
+ source_map_->RegisterModule(name, code);
+}
+
+void ModuleSystemTestEnvironment::RegisterModule(const std::string& name,
+ int resource_id) {
+ const std::string& code = ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(resource_id)
+ .as_string();
+ source_map_->RegisterModule(name, code);
+}
+
+void ModuleSystemTestEnvironment::OverrideNativeHandler(
+ const std::string& name,
+ const std::string& code) {
+ RegisterModule(name, code);
+ context_->module_system()->OverrideNativeHandlerForTest(name);
+}
+
+void ModuleSystemTestEnvironment::RegisterTestFile(
+ const std::string& module_name,
+ const std::string& file_name) {
+ base::FilePath test_js_file_path;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_js_file_path));
+ test_js_file_path = test_js_file_path.AppendASCII(file_name);
+ std::string test_js;
+ ASSERT_TRUE(base::ReadFileToString(test_js_file_path, &test_js));
+ source_map_->RegisterModule(module_name, test_js);
+}
+
+void ModuleSystemTestEnvironment::ShutdownGin() {
+ context_holder_.reset();
+}
+
+void ModuleSystemTestEnvironment::ShutdownModuleSystem() {
+ CHECK(context_->is_valid());
+ context_->v8_context()->Exit();
+ context_->Invalidate();
+}
+
+v8::Local<v8::Object> ModuleSystemTestEnvironment::CreateGlobal(
+ const std::string& name) {
+ v8::EscapableHandleScope handle_scope(isolate_);
+ v8::Local<v8::Object> object = v8::Object::New(isolate_);
+ isolate_->GetCurrentContext()->Global()->Set(
+ v8::String::NewFromUtf8(isolate_, name.c_str()), object);
+ return handle_scope.Escape(object);
+}
+
+ModuleSystemTest::ModuleSystemTest()
+ : isolate_(v8::Isolate::GetCurrent()),
+ should_assertions_be_made_(true) {
+}
+
+ModuleSystemTest::~ModuleSystemTest() {
+}
+
+void ModuleSystemTest::SetUp() {
+ env_ = CreateEnvironment();
+ base::CommandLine::ForCurrentProcess()->AppendSwitch("test-type");
+}
+
+void ModuleSystemTest::TearDown() {
+ // All tests must assert at least once unless otherwise specified.
+ EXPECT_EQ(should_assertions_be_made_,
+ env_->assert_natives()->assertion_made());
+ EXPECT_FALSE(env_->assert_natives()->failed());
+ env_.reset();
+ v8::HeapStatistics stats;
+ isolate_->GetHeapStatistics(&stats);
+ size_t old_heap_size = 0;
+ // Run the GC until the heap size reaches a steady state to ensure that
+ // all the garbage is collected.
+ while (stats.used_heap_size() != old_heap_size) {
+ old_heap_size = stats.used_heap_size();
+ isolate_->RequestGarbageCollectionForTesting(
+ v8::Isolate::kFullGarbageCollection);
+ isolate_->GetHeapStatistics(&stats);
+ }
+}
+
+scoped_ptr<ModuleSystemTestEnvironment> ModuleSystemTest::CreateEnvironment() {
+ return make_scoped_ptr(new ModuleSystemTestEnvironment(isolate_));
+}
+
+void ModuleSystemTest::ExpectNoAssertionsMade() {
+ should_assertions_be_made_ = false;
+}
+
+void ModuleSystemTest::RunResolvedPromises() {
+ v8::MicrotasksScope::PerformCheckpoint(isolate_);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/module_system_test.h b/chromium/extensions/renderer/module_system_test.h
new file mode 100644
index 00000000000..517c3080c00
--- /dev/null
+++ b/chromium/extensions/renderer/module_system_test.h
@@ -0,0 +1,111 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_MODULE_SYSTEM_TEST_H_
+#define EXTENSIONS_RENDERER_MODULE_SYSTEM_TEST_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/module_system.h"
+#include "extensions/renderer/script_context.h"
+#include "gin/public/context_holder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+class ModuleSystemTestEnvironment {
+ public:
+ class AssertNatives;
+ class StringSourceMap;
+
+ explicit ModuleSystemTestEnvironment(v8::Isolate* isolate);
+ ~ModuleSystemTestEnvironment();
+
+ // Register a named JS module in the module system.
+ void RegisterModule(const std::string& name, const std::string& code);
+
+ // Register a named JS module with source retrieved from a ResourceBundle.
+ void RegisterModule(const std::string& name, int resource_id);
+
+ // Register a named JS module in the module system and tell the module system
+ // to use it to handle any requireNative() calls for native modules with that
+ // name.
+ void OverrideNativeHandler(const std::string& name, const std::string& code);
+
+ // Registers |file_name| from chrome/test/data/extensions as a module name
+ // |module_name|.
+ void RegisterTestFile(const std::string& module_name,
+ const std::string& file_name);
+
+ // Create an empty object in the global scope with name |name|.
+ v8::Local<v8::Object> CreateGlobal(const std::string& name);
+
+ void ShutdownGin();
+
+ void ShutdownModuleSystem();
+
+ ModuleSystem* module_system() { return context_->module_system(); }
+
+ ScriptContext* context() { return context_.get(); }
+
+ v8::Isolate* isolate() { return isolate_; }
+
+ AssertNatives* assert_natives() { return assert_natives_; }
+
+ private:
+ v8::Isolate* isolate_;
+ scoped_ptr<gin::ContextHolder> context_holder_;
+ v8::HandleScope handle_scope_;
+ scoped_ptr<ScriptContext> context_;
+ AssertNatives* assert_natives_;
+ scoped_ptr<StringSourceMap> source_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(ModuleSystemTestEnvironment);
+};
+
+// Test fixture for testing JS that makes use of the module system.
+//
+// Typically tests will look like:
+//
+// TEST_F(MyModuleSystemTest, TestStuff) {
+// ModuleSystem::NativesEnabledScope natives_enabled(module_system_.get());
+// RegisterModule("test", "requireNative('assert').AssertTrue(true);");
+// module_system_->Require("test");
+// }
+//
+// By default a test will fail if no method in the native module 'assert' is
+// called. This behaviour can be overridden by calling ExpectNoAssertionsMade().
+class ModuleSystemTest : public testing::Test {
+ public:
+ ModuleSystemTest();
+ ~ModuleSystemTest() override;
+
+ void SetUp() override;
+ void TearDown() override;
+
+ protected:
+ ModuleSystemTestEnvironment* env() { return env_.get(); }
+
+ scoped_ptr<ModuleSystemTestEnvironment> CreateEnvironment();
+
+ // Make the test fail if any asserts are called. By default a test will fail
+ // if no asserts are called.
+ void ExpectNoAssertionsMade();
+
+ // Runs promises that have been resolved. Resolved promises will not run
+ // until this is called.
+ void RunResolvedPromises();
+
+ private:
+ v8::Isolate* isolate_;
+ scoped_ptr<ModuleSystemTestEnvironment> env_;
+ bool should_assertions_be_made_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ModuleSystemTest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_MODULE_SYSTEM_TEST_H_
diff --git a/chromium/extensions/renderer/module_system_unittest.cc b/chromium/extensions/renderer/module_system_unittest.cc
new file mode 100644
index 00000000000..5803ad0d694
--- /dev/null
+++ b/chromium/extensions/renderer/module_system_unittest.cc
@@ -0,0 +1,518 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/module_system.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/renderer/module_system_test.h"
+#include "gin/modules/module_registry.h"
+
+namespace extensions {
+
+class CounterNatives : public ObjectBackedNativeHandler {
+ public:
+ explicit CounterNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context), counter_(0) {
+ RouteFunction("Get",
+ base::Bind(&CounterNatives::Get, base::Unretained(this)));
+ RouteFunction(
+ "Increment",
+ base::Bind(&CounterNatives::Increment, base::Unretained(this)));
+ }
+
+ void Get(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(static_cast<int32_t>(counter_));
+ }
+
+ void Increment(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ counter_++;
+ }
+
+ private:
+ int counter_;
+};
+
+class TestExceptionHandler : public ModuleSystem::ExceptionHandler {
+ public:
+ TestExceptionHandler()
+ : ModuleSystem::ExceptionHandler(nullptr), handled_exception_(false) {}
+
+ void HandleUncaughtException(const v8::TryCatch& try_catch) override {
+ handled_exception_ = true;
+ }
+
+ bool handled_exception() const { return handled_exception_; }
+
+ private:
+ bool handled_exception_;
+};
+
+TEST_F(ModuleSystemTest, TestExceptionHandling) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ TestExceptionHandler* handler = new TestExceptionHandler;
+ scoped_ptr<ModuleSystem::ExceptionHandler> scoped_handler(handler);
+ ASSERT_FALSE(handler->handled_exception());
+ env()->module_system()->SetExceptionHandlerForTest(std::move(scoped_handler));
+
+ env()->RegisterModule("test", "throw 'hi';");
+ env()->module_system()->Require("test");
+ ASSERT_TRUE(handler->handled_exception());
+
+ ExpectNoAssertionsMade();
+}
+
+TEST_F(ModuleSystemTest, TestRequire) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("add",
+ "exports.$set('Add',"
+ "function(x, y) { return x + y; });");
+ env()->RegisterModule("test",
+ "var Add = require('add').Add;"
+ "requireNative('assert').AssertTrue(Add(3, 5) == 8);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestNestedRequire) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("add",
+ "exports.$set('Add',"
+ "function(x, y) { return x + y; });");
+ env()->RegisterModule("double",
+ "var Add = require('add').Add;"
+ "exports.$set('Double',"
+ "function(x) { return Add(x, x); });");
+ env()->RegisterModule("test",
+ "var Double = require('double').Double;"
+ "requireNative('assert').AssertTrue(Double(3) == 6);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestModuleInsulation) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("x",
+ "var x = 10;"
+ "exports.$set('X', function() { return x; });");
+ env()->RegisterModule("y",
+ "var x = 15;"
+ "require('x');"
+ "exports.$set('Y', function() { return x; });");
+ env()->RegisterModule("test",
+ "var Y = require('y').Y;"
+ "var X = require('x').X;"
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(!this.hasOwnProperty('x'));"
+ "assert.AssertTrue(Y() == 15);"
+ "assert.AssertTrue(X() == 10);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestNativesAreDisabledOutsideANativesEnabledScope) {
+ env()->RegisterModule("test",
+ "var assert;"
+ "try {"
+ " assert = requireNative('assert');"
+ "} catch (e) {"
+ " var caught = true;"
+ "}"
+ "if (assert) {"
+ " assert.AssertTrue(true);"
+ "}");
+ env()->module_system()->Require("test");
+ ExpectNoAssertionsMade();
+}
+
+TEST_F(ModuleSystemTest, TestNativesAreEnabledWithinANativesEnabledScope) {
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(true);");
+
+ {
+ ModuleSystem::NativesEnabledScope natives_enabled(env()->module_system());
+ {
+ ModuleSystem::NativesEnabledScope natives_enabled_inner(
+ env()->module_system());
+ }
+ env()->module_system()->Require("test");
+ }
+}
+
+TEST_F(ModuleSystemTest, TestLazyField) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("lazy", "exports.$set('x', 5);");
+
+ v8::Local<v8::Object> object = env()->CreateGlobal("object");
+
+ env()->module_system()->SetLazyField(object, "blah", "lazy", "x");
+
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(object.blah == 5);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestLazyFieldYieldingObject) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "lazy",
+ "var object = {};"
+ "object.__defineGetter__('z', function() { return 1; });"
+ "object.x = 5;"
+ "object.y = function() { return 10; };"
+ "exports.$set('object', object);");
+
+ v8::Local<v8::Object> object = env()->CreateGlobal("object");
+
+ env()->module_system()->SetLazyField(object, "thing", "lazy", "object");
+
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(object.thing.x == 5);"
+ "assert.AssertTrue(object.thing.y() == 10);"
+ "assert.AssertTrue(object.thing.z == 1);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestLazyFieldIsOnlyEvaledOnce) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->RegisterNativeHandler(
+ "counter",
+ scoped_ptr<NativeHandler>(new CounterNatives(env()->context())));
+ env()->RegisterModule("lazy",
+ "requireNative('counter').Increment();"
+ "exports.$set('x', 5);");
+
+ v8::Local<v8::Object> object = env()->CreateGlobal("object");
+
+ env()->module_system()->SetLazyField(object, "x", "lazy", "x");
+
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "var counter = requireNative('counter');"
+ "assert.AssertTrue(counter.Get() == 0);"
+ "object.x;"
+ "assert.AssertTrue(counter.Get() == 1);"
+ "object.x;"
+ "assert.AssertTrue(counter.Get() == 1);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestRequireNativesAfterLazyEvaluation) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("lazy", "exports.$set('x', 5);");
+ v8::Local<v8::Object> object = env()->CreateGlobal("object");
+
+ env()->module_system()->SetLazyField(object, "x", "lazy", "x");
+ env()->RegisterModule("test",
+ "object.x;"
+ "requireNative('assert').AssertTrue(true);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestTransitiveRequire) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("dependency", "exports.$set('x', 5);");
+ env()->RegisterModule("lazy",
+ "exports.$set('output', require('dependency'));");
+
+ v8::Local<v8::Object> object = env()->CreateGlobal("object");
+
+ env()->module_system()->SetLazyField(object, "thing", "lazy", "output");
+
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(object.thing.x == 5);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestModulesOnlyGetEvaledOnce) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->RegisterNativeHandler(
+ "counter",
+ scoped_ptr<NativeHandler>(new CounterNatives(env()->context())));
+
+ env()->RegisterModule("incrementsWhenEvaled",
+ "requireNative('counter').Increment();");
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "var counter = requireNative('counter');"
+ "assert.AssertTrue(counter.Get() == 0);"
+ "require('incrementsWhenEvaled');"
+ "assert.AssertTrue(counter.Get() == 1);"
+ "require('incrementsWhenEvaled');"
+ "assert.AssertTrue(counter.Get() == 1);");
+
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestOverrideNativeHandler) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->OverrideNativeHandler("assert",
+ "exports.$set('AssertTrue', function() {});");
+ env()->RegisterModule("test", "requireNative('assert').AssertTrue(true);");
+ ExpectNoAssertionsMade();
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestOverrideNonExistentNativeHandler) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->OverrideNativeHandler("thing", "exports.$set('x', 5);");
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(requireNative('thing').x == 5);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsync) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("add",
+ "define('add', [], function() {"
+ " return { Add: function(x, y) { return x + y; } };"
+ "});");
+ env()->RegisterModule("math",
+ "define('math', ['add'], function(add) {"
+ " return { Add: add.Add };"
+ "});");
+ env()->RegisterModule(
+ "test",
+ "requireAsync('math').then(function(math) {"
+ " requireNative('assert').AssertTrue(math.Add(3, 5) == 8);"
+ "});");
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncInParallel) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("add",
+ "define('add', [], function() {"
+ " return { Add: function(x, y) { return x + y; } };"
+ "});");
+ env()->RegisterModule(
+ "subtract",
+ "define('subtract', [], function() {"
+ " return { Subtract: function(x, y) { return x - y; } };"
+ "});");
+ env()->RegisterModule(
+ "math",
+ "exports.$set('AddAndSubtract', function(x, y, z) {"
+ " return Promise.all([requireAsync('add'),"
+ " requireAsync('subtract')"
+ " ]).then(function(modules) {"
+ " return modules[1].Subtract(modules[0].Add(x, y), z);"
+ " });"
+ "});");
+ env()->RegisterModule("test",
+ "var AddAndSubtract = require('math').AddAndSubtract;"
+ "AddAndSubtract(3, 5, 2).then(function(result) {"
+ " requireNative('assert').AssertTrue(result == 6);"
+ "});");
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestNestedRequireAsyncs) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("first",
+ "define('first', [], function() {"
+ " return { next: 'second' };"
+ "});");
+ env()->RegisterModule("second",
+ "define('second', [], function() {"
+ " return { next: '' };"
+ "});");
+ env()->RegisterModule(
+ "test",
+ "requireAsync('first').then(function(module) {"
+ " return requireAsync(module.next)"
+ "}).then(function(module) {"
+ " requireNative('assert').AssertTrue(module.next === '');"
+ "});");
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireFromAMDModule) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "add", "exports.$set('Add', function(x, y) { return x + y; });");
+ env()->RegisterModule("math",
+ "define('math', [], function() {"
+ " var add = require('add');"
+ " return { Add: add.Add };"
+ "});");
+ env()->RegisterModule(
+ "test",
+ "requireAsync('math').then(function(math) {"
+ " requireNative('assert').AssertTrue(math.Add(3, 5) == 8);"
+ "});");
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncFromAMDModule) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("add",
+ "define('add', [], function() {"
+ " return { Add: function(x, y) { return x + y; } };"
+ "});");
+ env()->RegisterModule("math",
+ "define('math', [], function() {"
+ " function Add(x, y) {"
+ " return requireAsync('add').then(function(add) {"
+ " return add.Add(x, y);"
+ " });"
+ " }"
+ " return { Add: Add };"
+ "});");
+ env()->RegisterModule("test",
+ "requireAsync('math').then(function(math) {"
+ " return math.Add(3, 6);"
+ "}).then(function(result) {"
+ " requireNative('assert').AssertTrue(result == 9);"
+ "});");
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncFromAnotherContext) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "requireAsync('natives').then(function(natives) {"
+ " natives.requireAsync('ping').then(function(ping) {"
+ " return ping();"
+ " }).then(function(result) {"
+ " requireNative('assert').AssertTrue(result == 'pong');"
+ " });"
+ "});");
+ scoped_ptr<ModuleSystemTestEnvironment> other_env = CreateEnvironment();
+ other_env->RegisterModule("ping",
+ "define('ping', ['natives'], function(natives) {"
+ " return function() {"
+ " return 'pong';"
+ " }"
+ "});");
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ other_env->module_system()->NewInstance());
+ gin::ModuleRegistry::From(other_env->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ env()->module_system()->NewInstance());
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncBetweenContexts) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("pong",
+ "define('pong', [], function() {"
+ " return function() { return 'done'; };"
+ "});");
+ env()->RegisterModule(
+ "test",
+ "requireAsync('natives').then(function(natives) {"
+ " natives.requireAsync('ping').then(function(ping) {"
+ " return ping();"
+ " }).then(function(pong) {"
+ " return pong();"
+ " }).then(function(result) {"
+ " requireNative('assert').AssertTrue(result == 'done');"
+ " });"
+ "});");
+ scoped_ptr<ModuleSystemTestEnvironment> other_env = CreateEnvironment();
+ other_env->RegisterModule("ping",
+ "define('ping', ['natives'], function(natives) {"
+ " return function() {"
+ " return natives.requireAsync('pong');"
+ " }"
+ "});");
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ other_env->module_system()->NewInstance());
+ gin::ModuleRegistry::From(other_env->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ env()->module_system()->NewInstance());
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncFromContextWithNoModuleRegistry) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "requireAsync('natives').then(function(natives) {"
+ " var AssertTrue = requireNative('assert').AssertTrue;"
+ " natives.requireAsync('foo').then(function() {"
+ " AssertTrue(false);"
+ " }).catch(function(error) {"
+ " AssertTrue(error.message == "
+ " 'Extension view no longer exists');"
+ " });"
+ "});");
+ scoped_ptr<ModuleSystemTestEnvironment> other_env = CreateEnvironment();
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ other_env->module_system()->NewInstance());
+ other_env->ShutdownGin();
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncFromContextWithNoModuleSystem) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "requireAsync('natives').then(function(natives) {"
+ " requireNative('assert').AssertTrue("
+ " natives.requireAsync('foo') === undefined);"
+ "});");
+ scoped_ptr<ModuleSystemTestEnvironment> other_env = CreateEnvironment();
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ other_env->module_system()->NewInstance());
+ other_env->ShutdownModuleSystem();
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestPrivatesIsPrivate) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var v = privates({});"
+ "requireNative('assert').AssertFalse(v instanceof Object);");
+ env()->module_system()->Require("test");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/mojo/keep_alive_client_unittest.cc b/chromium/extensions/renderer/mojo/keep_alive_client_unittest.cc
new file mode 100644
index 00000000000..022d2d2ee54
--- /dev/null
+++ b/chromium/extensions/renderer/mojo/keep_alive_client_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright 2014 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.
+
+#include <utility>
+
+#include "base/macros.h"
+#include "extensions/common/mojo/keep_alive.mojom.h"
+#include "extensions/renderer/api_test_base.h"
+#include "grit/extensions_renderer_resources.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+
+// A test launcher for tests for the stash client defined in
+// extensions/test/data/keep_alive_client_unittest.js.
+
+namespace extensions {
+namespace {
+
+// A KeepAlive implementation that calls provided callbacks on creation and
+// destruction.
+class TestKeepAlive : public KeepAlive {
+ public:
+ TestKeepAlive(const base::Closure& on_destruction,
+ mojo::InterfaceRequest<KeepAlive> keep_alive)
+ : on_destruction_(on_destruction),
+ binding_(this, std::move(keep_alive)) {}
+
+ ~TestKeepAlive() override { on_destruction_.Run(); }
+
+ static void Create(const base::Closure& on_creation,
+ const base::Closure& on_destruction,
+ mojo::InterfaceRequest<KeepAlive> keep_alive) {
+ new TestKeepAlive(on_destruction, std::move(keep_alive));
+ on_creation.Run();
+ }
+
+ private:
+ const base::Closure on_destruction_;
+ mojo::StrongBinding<KeepAlive> binding_;
+};
+
+} // namespace
+
+class KeepAliveClientTest : public ApiTestBase {
+ public:
+ KeepAliveClientTest() {}
+
+ void SetUp() override {
+ ApiTestBase::SetUp();
+ service_provider()->AddService(
+ base::Bind(&TestKeepAlive::Create,
+ base::Bind(&KeepAliveClientTest::KeepAliveCreated,
+ base::Unretained(this)),
+ base::Bind(&KeepAliveClientTest::KeepAliveDestroyed,
+ base::Unretained(this))));
+ created_keep_alive_ = false;
+ destroyed_keep_alive_ = false;
+ }
+
+ void WaitForKeepAlive() {
+ // Wait for a keep-alive to be created and destroyed.
+ while (!created_keep_alive_ || !destroyed_keep_alive_) {
+ base::RunLoop run_loop;
+ stop_run_loop_ = run_loop.QuitClosure();
+ run_loop.Run();
+ }
+ EXPECT_TRUE(created_keep_alive_);
+ EXPECT_TRUE(destroyed_keep_alive_);
+ }
+
+ private:
+ void KeepAliveCreated() {
+ created_keep_alive_ = true;
+ if (!stop_run_loop_.is_null())
+ stop_run_loop_.Run();
+ }
+ void KeepAliveDestroyed() {
+ destroyed_keep_alive_ = true;
+ if (!stop_run_loop_.is_null())
+ stop_run_loop_.Run();
+ }
+
+ bool created_keep_alive_;
+ bool destroyed_keep_alive_;
+ base::Closure stop_run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeepAliveClientTest);
+};
+
+TEST_F(KeepAliveClientTest, KeepAliveWithSuccessfulCall) {
+ RunTest("keep_alive_client_unittest.js", "testKeepAliveWithSuccessfulCall");
+ WaitForKeepAlive();
+}
+
+TEST_F(KeepAliveClientTest, KeepAliveWithError) {
+ RunTest("keep_alive_client_unittest.js", "testKeepAliveWithError");
+ WaitForKeepAlive();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/mojo/stash_client_unittest.cc b/chromium/extensions/renderer/mojo/stash_client_unittest.cc
new file mode 100644
index 00000000000..3f361cc2535
--- /dev/null
+++ b/chromium/extensions/renderer/mojo/stash_client_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright 2015 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.
+
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/browser/mojo/stash_backend.h"
+#include "extensions/common/mojo/stash.mojom.h"
+#include "extensions/renderer/api_test_base.h"
+#include "gin/dictionary.h"
+#include "grit/extensions_renderer_resources.h"
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+
+// A test launcher for tests for the stash client defined in
+// extensions/test/data/stash_client_unittest.js.
+
+namespace extensions {
+class StashClientTest : public ApiTestBase {
+ public:
+ StashClientTest() {}
+
+ void SetUp() override {
+ ApiTestBase::SetUp();
+ stash_backend_.reset(new StashBackend(base::Closure()));
+ PrepareEnvironment(api_test_env());
+ }
+
+ void PrepareEnvironment(ApiTestEnvironment* env) {
+ env->service_provider()->AddService(base::Bind(
+ &StashBackend::BindToRequest, base::Unretained(stash_backend_.get())));
+ }
+
+ scoped_ptr<StashBackend> stash_backend_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StashClientTest);
+};
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_StashAndRestore DISABLED_StashAndRestore
+#else
+#define MAYBE_StashAndRestore StashAndRestore
+#endif
+// Test that stashing and restoring work correctly.
+TEST_F(StashClientTest, MAYBE_StashAndRestore) {
+ ASSERT_NO_FATAL_FAILURE(RunTest("stash_client_unittest.js", "testStash"));
+ scoped_ptr<ModuleSystemTestEnvironment> restore_test_env(CreateEnvironment());
+ ApiTestEnvironment restore_environment(restore_test_env.get());
+ PrepareEnvironment(&restore_environment);
+ restore_environment.RunTest("stash_client_unittest.js", "testRetrieve");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/native_handler.cc b/chromium/extensions/renderer/native_handler.cc
new file mode 100644
index 00000000000..a8b343ad589
--- /dev/null
+++ b/chromium/extensions/renderer/native_handler.cc
@@ -0,0 +1,22 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/native_handler.h"
+
+#include "base/logging.h"
+
+namespace extensions {
+
+NativeHandler::NativeHandler() : is_valid_(true) {}
+
+NativeHandler::~NativeHandler() {
+ CHECK(!is_valid_) << "NativeHandlers must be invalidated before destruction";
+}
+
+void NativeHandler::Invalidate() {
+ CHECK(is_valid_);
+ is_valid_ = false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/native_handler.h b/chromium/extensions/renderer/native_handler.h
new file mode 100644
index 00000000000..5b3d3f73f5e
--- /dev/null
+++ b/chromium/extensions/renderer/native_handler.h
@@ -0,0 +1,49 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_NATIVE_HANDLER_H_
+
+#include "base/macros.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// NativeHandlers are intended to be used with a ModuleSystem. The ModuleSystem
+// will assume ownership of the NativeHandler, and as a ModuleSystem is tied to
+// a single v8::Context, this implies that NativeHandlers will also be tied to
+// a single v8::Context.
+// TODO(koz): Rename this to NativeJavaScriptModule.
+class NativeHandler {
+ public:
+ NativeHandler();
+ virtual ~NativeHandler();
+
+ // Create a new instance of the object this handler specifies.
+ virtual v8::Local<v8::Object> NewInstance() = 0;
+
+ // Invalidate this object so it cannot be used any more. This is needed
+ // because it's possible for this to outlive its owner context. Invalidate
+ // must be called before this happens.
+ //
+ // Subclasses should override to invalidate their own V8 state. If they do
+ // they must call their superclass' Invalidate().
+ //
+ // Invalidate() will be called on destruction, if it hasn't already been.
+ // Subclasses don't need to do it themselves.
+ virtual void Invalidate();
+
+ protected:
+ // Allow subclasses to query valid state.
+ bool is_valid() { return is_valid_; }
+
+ private:
+ bool is_valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/object_backed_native_handler.cc b/chromium/extensions/renderer/object_backed_native_handler.cc
new file mode 100644
index 00000000000..1ffc355cc48
--- /dev/null
+++ b/chromium/extensions/renderer/object_backed_native_handler.cc
@@ -0,0 +1,222 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
+#include "content/public/child/worker_thread.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/renderer/console.h"
+#include "extensions/renderer/module_system.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+namespace {
+// Key for the base::Bound routed function.
+const char* kHandlerFunction = "handler_function";
+const char* kFeatureName = "feature_name";
+} // namespace
+
+ObjectBackedNativeHandler::ObjectBackedNativeHandler(ScriptContext* context)
+ : router_data_(context->isolate()),
+ context_(context),
+ object_template_(context->isolate(),
+ v8::ObjectTemplate::New(context->isolate())) {
+}
+
+ObjectBackedNativeHandler::~ObjectBackedNativeHandler() {
+}
+
+v8::Local<v8::Object> ObjectBackedNativeHandler::NewInstance() {
+ return v8::Local<v8::ObjectTemplate>::New(GetIsolate(), object_template_)
+ ->NewInstance();
+}
+
+// static
+void ObjectBackedNativeHandler::Router(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Object> data = args.Data().As<v8::Object>();
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+
+ v8::Local<v8::Value> handler_function_value;
+ v8::Local<v8::Value> feature_name_value;
+ // See comment in header file for why we do this.
+ if (!GetPrivate(context, data, kHandlerFunction, &handler_function_value) ||
+ handler_function_value->IsUndefined() ||
+ !GetPrivate(context, data, kFeatureName, &feature_name_value) ||
+ !feature_name_value->IsString()) {
+ ScriptContext* script_context =
+ ScriptContextSet::GetContextByV8Context(context);
+ console::Error(script_context ? script_context->GetRenderFrame() : nullptr,
+ "Extension view no longer exists");
+ return;
+ }
+
+ // We can't access the ScriptContextSet on a worker thread. Luckily, we also
+ // don't inject many bindings into worker threads.
+ // TODO(devlin): Figure out a way around this.
+ if (content::WorkerThread::GetCurrentId() == 0) {
+ ScriptContext* script_context =
+ ScriptContextSet::GetContextByV8Context(context);
+ v8::Local<v8::String> feature_name_string =
+ feature_name_value->ToString(context).ToLocalChecked();
+ std::string feature_name = *v8::String::Utf8Value(feature_name_string);
+ // TODO(devlin): Eventually, we should fail if either script_context is null
+ // or feature_name is empty.
+ if (script_context &&
+ !feature_name.empty() &&
+ !script_context->GetAvailability(feature_name).is_available()) {
+ return;
+ }
+ }
+ // This CHECK is *important*. Otherwise, we'll go around happily executing
+ // something random. See crbug.com/548273.
+ CHECK(handler_function_value->IsExternal());
+ static_cast<HandlerFunction*>(
+ handler_function_value.As<v8::External>()->Value())->Run(args);
+
+ // Verify that the return value, if any, is accessible by the context.
+ v8::ReturnValue<v8::Value> ret = args.GetReturnValue();
+ v8::Local<v8::Value> ret_value = ret.Get();
+ if (ret_value->IsObject() && !ret_value->IsNull() &&
+ !ContextCanAccessObject(context, v8::Local<v8::Object>::Cast(ret_value),
+ true)) {
+ NOTREACHED() << "Insecure return value";
+ ret.SetUndefined();
+ }
+}
+
+void ObjectBackedNativeHandler::RouteFunction(
+ const std::string& name,
+ const HandlerFunction& handler_function) {
+ RouteFunction(name, "", handler_function);
+}
+
+void ObjectBackedNativeHandler::RouteFunction(
+ const std::string& name,
+ const std::string& feature_name,
+ const HandlerFunction& handler_function) {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context_->v8_context());
+
+ v8::Local<v8::Object> data = v8::Object::New(isolate);
+ SetPrivate(data, kHandlerFunction,
+ v8::External::New(isolate, new HandlerFunction(handler_function)));
+ DCHECK(feature_name.empty() ||
+ ExtensionAPI::GetSharedInstance()->GetFeatureDependency(feature_name))
+ << feature_name;
+ SetPrivate(data, kFeatureName,
+ v8_helpers::ToV8StringUnsafe(isolate, feature_name));
+ v8::Local<v8::FunctionTemplate> function_template =
+ v8::FunctionTemplate::New(isolate, Router, data);
+ v8::Local<v8::ObjectTemplate>::New(isolate, object_template_)
+ ->Set(isolate, name.c_str(), function_template);
+ router_data_.Append(data);
+}
+
+v8::Isolate* ObjectBackedNativeHandler::GetIsolate() const {
+ return context_->isolate();
+}
+
+void ObjectBackedNativeHandler::Invalidate() {
+ v8::Isolate* isolate = GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context_->v8_context());
+
+ for (size_t i = 0; i < router_data_.Size(); i++) {
+ v8::Local<v8::Object> data = router_data_.Get(i);
+ v8::Local<v8::Value> handler_function_value;
+ CHECK(GetPrivate(data, kHandlerFunction, &handler_function_value));
+ delete static_cast<HandlerFunction*>(
+ handler_function_value.As<v8::External>()->Value());
+ DeletePrivate(data, kHandlerFunction);
+ }
+
+ router_data_.Clear();
+ object_template_.Reset();
+
+ NativeHandler::Invalidate();
+}
+
+// static
+bool ObjectBackedNativeHandler::ContextCanAccessObject(
+ const v8::Local<v8::Context>& context,
+ const v8::Local<v8::Object>& object,
+ bool allow_null_context) {
+ if (object->IsNull())
+ return true;
+ if (context == object->CreationContext())
+ return true;
+ ScriptContext* other_script_context =
+ ScriptContextSet::GetContextByObject(object);
+ if (!other_script_context || !other_script_context->web_frame())
+ return allow_null_context;
+
+ return blink::WebFrame::scriptCanAccess(other_script_context->web_frame());
+}
+
+void ObjectBackedNativeHandler::SetPrivate(v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value> value) {
+ SetPrivate(context_->v8_context(), obj, key, value);
+}
+
+// static
+void ObjectBackedNativeHandler::SetPrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value> value) {
+ obj->SetPrivate(context, v8::Private::ForApi(context->GetIsolate(),
+ v8::String::NewFromUtf8(
+ context->GetIsolate(), key)),
+ value)
+ .FromJust();
+}
+
+bool ObjectBackedNativeHandler::GetPrivate(v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value>* result) {
+ return GetPrivate(context_->v8_context(), obj, key, result);
+}
+
+// static
+bool ObjectBackedNativeHandler::GetPrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value>* result) {
+ return obj->GetPrivate(context,
+ v8::Private::ForApi(context->GetIsolate(),
+ v8::String::NewFromUtf8(
+ context->GetIsolate(), key)))
+ .ToLocal(result);
+}
+
+void ObjectBackedNativeHandler::DeletePrivate(v8::Local<v8::Object> obj,
+ const char* key) {
+ DeletePrivate(context_->v8_context(), obj, key);
+}
+
+// static
+void ObjectBackedNativeHandler::DeletePrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key) {
+ obj->DeletePrivate(context,
+ v8::Private::ForApi(
+ context->GetIsolate(),
+ v8::String::NewFromUtf8(context->GetIsolate(), key)))
+ .FromJust();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/object_backed_native_handler.h b/chromium/extensions/renderer/object_backed_native_handler.h
new file mode 100644
index 00000000000..974abe18600
--- /dev/null
+++ b/chromium/extensions/renderer/object_backed_native_handler.h
@@ -0,0 +1,123 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_OBJECT_BACKED_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_OBJECT_BACKED_NATIVE_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "extensions/renderer/native_handler.h"
+#include "v8/include/v8-util.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// An ObjectBackedNativeHandler is a factory for JS objects with functions on
+// them that map to native C++ functions. Subclasses should call RouteFunction()
+// in their constructor to define functions on the created JS objects.
+class ObjectBackedNativeHandler : public NativeHandler {
+ public:
+ explicit ObjectBackedNativeHandler(ScriptContext* context);
+ ~ObjectBackedNativeHandler() override;
+
+ // Create an object with bindings to the native functions defined through
+ // RouteFunction().
+ v8::Local<v8::Object> NewInstance() override;
+
+ v8::Isolate* GetIsolate() const;
+
+ protected:
+ typedef base::Callback<void(const v8::FunctionCallbackInfo<v8::Value>&)>
+ HandlerFunction;
+
+ // Installs a new 'route' from |name| to |handler_function|. This means that
+ // NewInstance()s of this ObjectBackedNativeHandler will have a property
+ // |name| which will be handled by |handler_function|.
+ //
+ // Routed functions are destroyed along with the destruction of this class,
+ // and are never called back into, therefore it's safe for |handler_function|
+ // to bind to base::Unretained.
+ //
+ // |feature_name| corresponds to the api feature the native handler is used
+ // for. If the associated ScriptContext does not have access to that feature,
+ // the |handler_function| is not invoked.
+ // TODO(devlin): Deprecate the version that doesn't take a |feature_name|.
+ void RouteFunction(const std::string& name,
+ const HandlerFunction& handler_function);
+ void RouteFunction(const std::string& name,
+ const std::string& feature_name,
+ const HandlerFunction& handler_function);
+
+ ScriptContext* context() const { return context_; }
+
+ void Invalidate() override;
+
+ // Returns true if the given |context| is allowed to access the given
+ // |object|. This should be checked before returning any objects from another
+ // context.
+ // |allow_null_context| indicates that if there is no ScriptContext associated
+ // with the |object|, it should be allowed.
+ // TODO(devlin): It'd be nice to track down when when there's no ScriptContext
+ // and remove |allow_null_context|.
+ static bool ContextCanAccessObject(const v8::Local<v8::Context>& context,
+ const v8::Local<v8::Object>& object,
+ bool allow_null_context);
+
+ // The following methods are convenience wrappers for methods on v8::Object
+ // with the corresponding names.
+ void SetPrivate(v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value> value);
+ static void SetPrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value> value);
+ bool GetPrivate(v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value>* result);
+ static bool GetPrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value>* result);
+ void DeletePrivate(v8::Local<v8::Object> obj, const char* key);
+ static void DeletePrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key);
+
+ private:
+ // Callback for RouteFunction which routes the V8 call to the correct
+ // base::Bound callback.
+ static void Router(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // When RouteFunction is called we create a v8::Object to hold the data we
+ // need when handling it in Router() - this is the base::Bound function to
+ // route to.
+ //
+ // We need a v8::Object because it's possible for v8 to outlive the
+ // base::Bound function; the lifetime of an ObjectBackedNativeHandler is the
+ // lifetime of webkit's involvement with it, not the life of the v8 context.
+ // A scenario when v8 will outlive us is if a frame holds onto the
+ // contentWindow of an iframe after it's removed.
+ //
+ // So, we use v8::Objects here to hold that data, effectively refcounting
+ // the data. When |this| is destroyed we remove the base::Bound function from
+ // the object to indicate that it shoudn't be called.
+ typedef v8::PersistentValueVector<v8::Object> RouterData;
+ RouterData router_data_;
+
+ ScriptContext* context_;
+
+ v8::Global<v8::ObjectTemplate> object_template_;
+
+ DISALLOW_COPY_AND_ASSIGN(ObjectBackedNativeHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_OBJECT_BACKED_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/print_native_handler.cc b/chromium/extensions/renderer/print_native_handler.cc
new file mode 100644
index 00000000000..b6ee63a7e59
--- /dev/null
+++ b/chromium/extensions/renderer/print_native_handler.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/print_native_handler.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/strings/string_util.h"
+
+namespace extensions {
+
+PrintNativeHandler::PrintNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("Print",
+ base::Bind(&PrintNativeHandler::Print, base::Unretained(this)));
+}
+
+void PrintNativeHandler::Print(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() < 1)
+ return;
+
+ std::vector<std::string> components;
+ for (int i = 0; i < args.Length(); ++i)
+ components.push_back(*v8::String::Utf8Value(args[i]));
+
+ LOG(ERROR) << base::JoinString(components, ",");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/print_native_handler.h b/chromium/extensions/renderer/print_native_handler.h
new file mode 100644
index 00000000000..b2e5d375d7f
--- /dev/null
+++ b/chromium/extensions/renderer/print_native_handler.h
@@ -0,0 +1,21 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_PRINT_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_PRINT_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class PrintNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit PrintNativeHandler(ScriptContext* context);
+
+ void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_PRINT_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/process_info_native_handler.cc b/chromium/extensions/renderer/process_info_native_handler.cc
new file mode 100644
index 00000000000..545a21274dc
--- /dev/null
+++ b/chromium/extensions/renderer/process_info_native_handler.cc
@@ -0,0 +1,98 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/process_info_native_handler.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+ProcessInfoNativeHandler::ProcessInfoNativeHandler(
+ ScriptContext* context,
+ const std::string& extension_id,
+ const std::string& context_type,
+ bool is_incognito_context,
+ bool is_component_extension,
+ int manifest_version,
+ bool send_request_disabled)
+ : ObjectBackedNativeHandler(context),
+ extension_id_(extension_id),
+ context_type_(context_type),
+ is_incognito_context_(is_incognito_context),
+ is_component_extension_(is_component_extension),
+ manifest_version_(manifest_version),
+ send_request_disabled_(send_request_disabled) {
+ RouteFunction("GetExtensionId",
+ base::Bind(&ProcessInfoNativeHandler::GetExtensionId,
+ base::Unretained(this)));
+ RouteFunction("GetContextType",
+ base::Bind(&ProcessInfoNativeHandler::GetContextType,
+ base::Unretained(this)));
+ RouteFunction("InIncognitoContext",
+ base::Bind(&ProcessInfoNativeHandler::InIncognitoContext,
+ base::Unretained(this)));
+ RouteFunction("IsComponentExtension",
+ base::Bind(&ProcessInfoNativeHandler::IsComponentExtension,
+ base::Unretained(this)));
+ RouteFunction("GetManifestVersion",
+ base::Bind(&ProcessInfoNativeHandler::GetManifestVersion,
+ base::Unretained(this)));
+ RouteFunction("IsSendRequestDisabled",
+ base::Bind(&ProcessInfoNativeHandler::IsSendRequestDisabled,
+ base::Unretained(this)));
+ RouteFunction(
+ "HasSwitch",
+ base::Bind(&ProcessInfoNativeHandler::HasSwitch, base::Unretained(this)));
+}
+
+void ProcessInfoNativeHandler::GetExtensionId(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(args.GetIsolate(), extension_id_.c_str()));
+}
+
+void ProcessInfoNativeHandler::GetContextType(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(args.GetIsolate(), context_type_.c_str()));
+}
+
+void ProcessInfoNativeHandler::InIncognitoContext(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(is_incognito_context_);
+}
+
+void ProcessInfoNativeHandler::IsComponentExtension(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(is_component_extension_);
+}
+
+void ProcessInfoNativeHandler::GetManifestVersion(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(static_cast<int32_t>(manifest_version_));
+}
+
+void ProcessInfoNativeHandler::IsSendRequestDisabled(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (send_request_disabled_) {
+ args.GetReturnValue().Set(v8::String::NewFromUtf8(
+ args.GetIsolate(),
+ "sendRequest and onRequest are obsolete."
+ " Please use sendMessage and onMessage instead."));
+ }
+}
+
+void ProcessInfoNativeHandler::HasSwitch(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 1 && args[0]->IsString());
+ bool has_switch = base::CommandLine::ForCurrentProcess()->HasSwitch(
+ *v8::String::Utf8Value(args[0]));
+ args.GetReturnValue().Set(v8::Boolean::New(args.GetIsolate(), has_switch));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/process_info_native_handler.h b/chromium/extensions/renderer/process_info_native_handler.h
new file mode 100644
index 00000000000..dcde4300807
--- /dev/null
+++ b/chromium/extensions/renderer/process_info_native_handler.h
@@ -0,0 +1,43 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_PROCESS_INFO_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_PROCESS_INFO_NATIVE_HANDLER_H_
+
+#include <string>
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class ProcessInfoNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ ProcessInfoNativeHandler(ScriptContext* context,
+ const std::string& extension_id,
+ const std::string& context_type,
+ bool is_incognito_context,
+ bool is_component_extension,
+ int manifest_version,
+ bool send_request_disabled);
+
+ private:
+ void GetExtensionId(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetContextType(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void InIncognitoContext(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void IsComponentExtension(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetManifestVersion(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void IsSendRequestDisabled(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void HasSwitch(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ std::string extension_id_;
+ std::string context_type_;
+ bool is_incognito_context_;
+ bool is_component_extension_;
+ int manifest_version_;
+ bool send_request_disabled_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_PROCESS_INFO_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/programmatic_script_injector.cc b/chromium/extensions/renderer/programmatic_script_injector.cc
new file mode 100644
index 00000000000..eea394b0aa8
--- /dev/null
+++ b/chromium/extensions/renderer/programmatic_script_injector.cc
@@ -0,0 +1,185 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/programmatic_script_injector.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/values.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/renderer/injection_host.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebScriptSource.h"
+
+namespace extensions {
+
+ProgrammaticScriptInjector::ProgrammaticScriptInjector(
+ const ExtensionMsg_ExecuteCode_Params& params,
+ content::RenderFrame* render_frame)
+ : params_(new ExtensionMsg_ExecuteCode_Params(params)),
+ url_(
+ ScriptContext::GetDataSourceURLForFrame(render_frame->GetWebFrame())),
+ finished_(false) {
+ if (url_.SchemeIs(url::kAboutScheme)) {
+ origin_for_about_error_ =
+ render_frame->GetWebFrame()->getSecurityOrigin().toString().utf8();
+ }
+}
+
+ProgrammaticScriptInjector::~ProgrammaticScriptInjector() {
+}
+
+UserScript::InjectionType ProgrammaticScriptInjector::script_type()
+ const {
+ return UserScript::PROGRAMMATIC_SCRIPT;
+}
+
+bool ProgrammaticScriptInjector::ShouldExecuteInMainWorld() const {
+ return params_->in_main_world;
+}
+
+bool ProgrammaticScriptInjector::IsUserGesture() const {
+ return params_->user_gesture;
+}
+
+bool ProgrammaticScriptInjector::ExpectsResults() const {
+ return params_->wants_result;
+}
+
+bool ProgrammaticScriptInjector::ShouldInjectJs(
+ UserScript::RunLocation run_location) const {
+ return GetRunLocation() == run_location && params_->is_javascript;
+}
+
+bool ProgrammaticScriptInjector::ShouldInjectCss(
+ UserScript::RunLocation run_location) const {
+ return GetRunLocation() == run_location && !params_->is_javascript;
+}
+
+PermissionsData::AccessType ProgrammaticScriptInjector::CanExecuteOnFrame(
+ const InjectionHost* injection_host,
+ blink::WebLocalFrame* frame,
+ int tab_id) const {
+ GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
+ frame, frame->document().url(), params_->match_about_blank);
+ if (params_->is_web_view) {
+ if (frame->parent()) {
+ // This is a subframe inside <webview>, so allow it.
+ return PermissionsData::ACCESS_ALLOWED;
+ }
+
+ return effective_document_url == params_->webview_src
+ ? PermissionsData::ACCESS_ALLOWED
+ : PermissionsData::ACCESS_DENIED;
+ }
+ DCHECK_EQ(injection_host->id().type(), HostID::EXTENSIONS);
+
+ return injection_host->CanExecuteOnFrame(
+ effective_document_url,
+ content::RenderFrame::FromWebFrame(frame),
+ tab_id,
+ true /* is_declarative */);
+}
+
+std::vector<blink::WebScriptSource> ProgrammaticScriptInjector::GetJsSources(
+ UserScript::RunLocation run_location) const {
+ DCHECK_EQ(GetRunLocation(), run_location);
+ DCHECK(params_->is_javascript);
+
+ return std::vector<blink::WebScriptSource>(
+ 1,
+ blink::WebScriptSource(
+ blink::WebString::fromUTF8(params_->code), params_->file_url));
+}
+
+std::vector<std::string> ProgrammaticScriptInjector::GetCssSources(
+ UserScript::RunLocation run_location) const {
+ DCHECK_EQ(GetRunLocation(), run_location);
+ DCHECK(!params_->is_javascript);
+
+ return std::vector<std::string>(1, params_->code);
+}
+
+void ProgrammaticScriptInjector::GetRunInfo(
+ ScriptsRunInfo* scripts_run_info,
+ UserScript::RunLocation run_location) const {
+}
+
+void ProgrammaticScriptInjector::OnInjectionComplete(
+ scoped_ptr<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) {
+ DCHECK(results_.empty());
+ if (execution_result)
+ results_.Append(std::move(execution_result));
+ Finish(std::string(), render_frame);
+}
+
+void ProgrammaticScriptInjector::OnWillNotInject(
+ InjectFailureReason reason,
+ content::RenderFrame* render_frame) {
+ std::string error;
+ switch (reason) {
+ case NOT_ALLOWED:
+ if (!CanShowUrlInError()) {
+ error = manifest_errors::kCannotAccessPage;
+ } else if (!origin_for_about_error_.empty()) {
+ error = ErrorUtils::FormatErrorMessage(
+ manifest_errors::kCannotAccessAboutUrl, url_.spec(),
+ origin_for_about_error_);
+ } else {
+ error = ErrorUtils::FormatErrorMessage(
+ manifest_errors::kCannotAccessPageWithUrl, url_.spec());
+ }
+ break;
+ case EXTENSION_REMOVED: // no special error here.
+ case WONT_INJECT:
+ break;
+ }
+ Finish(error, render_frame);
+}
+
+bool ProgrammaticScriptInjector::CanShowUrlInError() const {
+ if (params_->host_id.type() != HostID::EXTENSIONS)
+ return false;
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(params_->host_id.id());
+ if (!extension)
+ return false;
+ return extension->permissions_data()->active_permissions().HasAPIPermission(
+ APIPermission::kTab);
+}
+
+UserScript::RunLocation ProgrammaticScriptInjector::GetRunLocation() const {
+ return static_cast<UserScript::RunLocation>(params_->run_at);
+}
+
+void ProgrammaticScriptInjector::Finish(const std::string& error,
+ content::RenderFrame* render_frame) {
+ DCHECK(!finished_);
+ finished_ = true;
+
+ // It's possible that the render frame was destroyed in the course of
+ // injecting scripts. Don't respond if it was (the browser side watches for
+ // frame deletions so nothing is left hanging).
+ if (render_frame) {
+ render_frame->Send(
+ new ExtensionHostMsg_ExecuteCodeFinished(
+ render_frame->GetRoutingID(), params_->request_id,
+ error, url_, results_));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/programmatic_script_injector.h b/chromium/extensions/renderer/programmatic_script_injector.h
new file mode 100644
index 00000000000..a7af9ff3818
--- /dev/null
+++ b/chromium/extensions/renderer/programmatic_script_injector.h
@@ -0,0 +1,84 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_PROGRAMMATIC_SCRIPT_INJECTOR_H_
+#define EXTENSIONS_RENDERER_PROGRAMMATIC_SCRIPT_INJECTOR_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/renderer/script_injection.h"
+#include "url/gurl.h"
+
+struct ExtensionMsg_ExecuteCode_Params;
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+
+// A ScriptInjector to handle tabs.executeScript().
+class ProgrammaticScriptInjector : public ScriptInjector {
+ public:
+ ProgrammaticScriptInjector(const ExtensionMsg_ExecuteCode_Params& params,
+ content::RenderFrame* render_frame);
+ ~ProgrammaticScriptInjector() override;
+
+ private:
+ // ScriptInjector implementation.
+ UserScript::InjectionType script_type() const override;
+ bool ShouldExecuteInMainWorld() const override;
+ bool IsUserGesture() const override;
+ bool ExpectsResults() const override;
+ bool ShouldInjectJs(UserScript::RunLocation run_location) const override;
+ bool ShouldInjectCss(UserScript::RunLocation run_location) const override;
+ PermissionsData::AccessType CanExecuteOnFrame(
+ const InjectionHost* injection_host,
+ blink::WebLocalFrame* web_frame,
+ int tab_id) const override;
+ std::vector<blink::WebScriptSource> GetJsSources(
+ UserScript::RunLocation run_location) const override;
+ std::vector<std::string> GetCssSources(
+ UserScript::RunLocation run_location) const override;
+ void GetRunInfo(ScriptsRunInfo* scripts_run_info,
+ UserScript::RunLocation run_location) const override;
+ void OnInjectionComplete(scoped_ptr<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) override;
+ void OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) override;
+
+ // Whether it is safe to include information about the URL in error messages.
+ bool CanShowUrlInError() const;
+
+ // Return the run location for this injector.
+ UserScript::RunLocation GetRunLocation() const;
+
+ // Notify the browser that the script was injected (or never will be), and
+ // send along any results or errors.
+ void Finish(const std::string& error, content::RenderFrame* render_frame);
+
+ // The parameters for injecting the script.
+ scoped_ptr<ExtensionMsg_ExecuteCode_Params> params_;
+
+ // The url of the frame into which we are injecting.
+ GURL url_;
+
+ // The serialization of the frame's origin if the frame is an about:-URL. This
+ // is used to provide user-friendly messages.
+ std::string origin_for_about_error_;
+
+ // The results of the script execution.
+ base::ListValue results_;
+
+ // Whether or not this script injection has finished.
+ bool finished_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProgrammaticScriptInjector);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_PROGRAMMATIC_SCRIPT_INJECTOR_H_
diff --git a/chromium/extensions/renderer/render_frame_observer_natives.cc b/chromium/extensions/renderer/render_frame_observer_natives.cc
new file mode 100644
index 00000000000..7c507b37993
--- /dev/null
+++ b/chromium/extensions/renderer/render_frame_observer_natives.cc
@@ -0,0 +1,107 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/render_frame_observer_natives.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+namespace {
+
+// Deletes itself when done.
+class LoadWatcher : public content::RenderFrameObserver {
+ public:
+ LoadWatcher(content::RenderFrame* frame,
+ const base::Callback<void(bool)>& callback)
+ : content::RenderFrameObserver(frame), callback_(callback) {}
+
+ void DidCreateDocumentElement() override {
+ // Defer the callback instead of running it now to avoid re-entrancy caused
+ // by the JavaScript callback.
+ ExtensionFrameHelper::Get(render_frame())
+ ->ScheduleAtDocumentStart(base::Bind(callback_, true));
+ delete this;
+ }
+
+ void DidFailProvisionalLoad(const blink::WebURLError& error) override {
+ // Use PostTask to avoid running user scripts while handling this
+ // DidFailProvisionalLoad notification.
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(callback_, false));
+ delete this;
+ }
+
+ private:
+ base::Callback<void(bool)> callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoadWatcher);
+};
+
+} // namespace
+
+RenderFrameObserverNatives::RenderFrameObserverNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context), weak_ptr_factory_(this) {
+ RouteFunction(
+ "OnDocumentElementCreated",
+ base::Bind(&RenderFrameObserverNatives::OnDocumentElementCreated,
+ base::Unretained(this)));
+}
+
+RenderFrameObserverNatives::~RenderFrameObserverNatives() {}
+
+void RenderFrameObserverNatives::Invalidate() {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ ObjectBackedNativeHandler::Invalidate();
+}
+
+void RenderFrameObserverNatives::OnDocumentElementCreated(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 2);
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsFunction());
+
+ int frame_id = args[0]->Int32Value();
+
+ content::RenderFrame* frame = content::RenderFrame::FromRoutingID(frame_id);
+ if (!frame) {
+ LOG(WARNING) << "No render frame found to register LoadWatcher.";
+ return;
+ }
+
+ v8::Global<v8::Function> v8_callback(context()->isolate(),
+ args[1].As<v8::Function>());
+ base::Callback<void(bool)> callback(
+ base::Bind(&RenderFrameObserverNatives::InvokeCallback,
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(&v8_callback)));
+ if (ExtensionFrameHelper::Get(frame)->did_create_current_document_element()) {
+ // If the document element is already created, then we can call the callback
+ // immediately (though use PostTask to ensure that the callback is called
+ // asynchronously).
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(callback, true));
+ } else {
+ new LoadWatcher(frame, callback);
+ }
+
+ args.GetReturnValue().Set(true);
+}
+
+void RenderFrameObserverNatives::InvokeCallback(
+ v8::Global<v8::Function> callback,
+ bool succeeded) {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Value> args[] = {v8::Boolean::New(isolate, succeeded)};
+ context()->CallFunction(v8::Local<v8::Function>::New(isolate, callback),
+ arraysize(args), args);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/render_frame_observer_natives.h b/chromium/extensions/renderer/render_frame_observer_natives.h
new file mode 100644
index 00000000000..4e81e3bc8b7
--- /dev/null
+++ b/chromium/extensions/renderer/render_frame_observer_natives.h
@@ -0,0 +1,38 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_RENDER_FRAME_OBSERVER_NATIVES_H_
+#define EXTENSIONS_RENDERER_RENDER_FRAME_OBSERVER_NATIVES_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Native functions for JS to run callbacks upon RenderFrame events.
+class RenderFrameObserverNatives : public ObjectBackedNativeHandler {
+ public:
+ explicit RenderFrameObserverNatives(ScriptContext* context);
+ ~RenderFrameObserverNatives() override;
+
+ private:
+ void Invalidate() override;
+
+ // Runs a callback upon creation of new document element inside a render frame
+ // (document.documentElement).
+ void OnDocumentElementCreated(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ void InvokeCallback(v8::Global<v8::Function> callback, bool succeeded);
+
+ base::WeakPtrFactory<RenderFrameObserverNatives> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderFrameObserverNatives);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_RENDER_FRAME_OBSERVER_NATIVES_H_
diff --git a/chromium/extensions/renderer/renderer_extension_registry.cc b/chromium/extensions/renderer/renderer_extension_registry.cc
new file mode 100644
index 00000000000..ddc3a1b89f3
--- /dev/null
+++ b/chromium/extensions/renderer/renderer_extension_registry.cc
@@ -0,0 +1,105 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/renderer_extension_registry.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "content/public/renderer/render_thread.h"
+
+namespace extensions {
+
+namespace {
+
+base::LazyInstance<RendererExtensionRegistry> g_renderer_extension_registry =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+RendererExtensionRegistry::RendererExtensionRegistry() {}
+
+RendererExtensionRegistry::~RendererExtensionRegistry() {}
+
+// static
+RendererExtensionRegistry* RendererExtensionRegistry::Get() {
+ return g_renderer_extension_registry.Pointer();
+}
+
+const ExtensionSet* RendererExtensionRegistry::GetMainThreadExtensionSet()
+ const {
+ // This can only be modified on the RenderThread, because
+ // GetMainThreadExtensionSet is inherently thread unsafe.
+ // Enforcing single-thread modification at least mitigates this.
+ // TODO(annekao): Remove this restriction once GetMainThreadExtensionSet is
+ // fixed.
+ DCHECK(content::RenderThread::Get());
+ base::AutoLock lock(lock_);
+ return &extensions_;
+}
+
+size_t RendererExtensionRegistry::size() const {
+ base::AutoLock lock(lock_);
+ return extensions_.size();
+}
+
+bool RendererExtensionRegistry::is_empty() const {
+ base::AutoLock lock(lock_);
+ return extensions_.is_empty();
+}
+
+bool RendererExtensionRegistry::Contains(
+ const std::string& extension_id) const {
+ base::AutoLock lock(lock_);
+ return extensions_.Contains(extension_id);
+}
+
+bool RendererExtensionRegistry::Insert(
+ const scoped_refptr<const Extension>& extension) {
+ DCHECK(content::RenderThread::Get());
+ base::AutoLock lock(lock_);
+ return extensions_.Insert(extension);
+}
+
+bool RendererExtensionRegistry::Remove(const std::string& id) {
+ DCHECK(content::RenderThread::Get());
+ base::AutoLock lock(lock_);
+ return extensions_.Remove(id);
+}
+
+std::string RendererExtensionRegistry::GetExtensionOrAppIDByURL(
+ const GURL& url) const {
+ base::AutoLock lock(lock_);
+ return extensions_.GetExtensionOrAppIDByURL(url);
+}
+
+const Extension* RendererExtensionRegistry::GetExtensionOrAppByURL(
+ const GURL& url) const {
+ base::AutoLock lock(lock_);
+ return extensions_.GetExtensionOrAppByURL(url);
+}
+
+const Extension* RendererExtensionRegistry::GetHostedAppByURL(
+ const GURL& url) const {
+ base::AutoLock lock(lock_);
+ return extensions_.GetHostedAppByURL(url);
+}
+
+const Extension* RendererExtensionRegistry::GetByID(
+ const std::string& id) const {
+ base::AutoLock lock(lock_);
+ return extensions_.GetByID(id);
+}
+
+ExtensionIdSet RendererExtensionRegistry::GetIDs() const {
+ base::AutoLock lock(lock_);
+ return extensions_.GetIDs();
+}
+
+bool RendererExtensionRegistry::ExtensionBindingsAllowed(
+ const GURL& url) const {
+ base::AutoLock lock(lock_);
+ return extensions_.ExtensionBindingsAllowed(url);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/renderer_extension_registry.h b/chromium/extensions/renderer/renderer_extension_registry.h
new file mode 100644
index 00000000000..97c24bbf535
--- /dev/null
+++ b/chromium/extensions/renderer/renderer_extension_registry.h
@@ -0,0 +1,62 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_RENDERER_EXTENSION_REGISTRY_H_
+#define EXTENSIONS_RENDERER_RENDERER_EXTENSION_REGISTRY_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "extensions/common/extension_set.h"
+
+class GURL;
+
+namespace extensions {
+
+// Thread safe container for all loaded extensions in this process,
+// essentially the renderer counterpart to ExtensionRegistry.
+class RendererExtensionRegistry {
+ public:
+ RendererExtensionRegistry();
+ ~RendererExtensionRegistry();
+
+ static RendererExtensionRegistry* Get();
+
+ // Returns the ExtensionSet that underlies this RenderExtensionRegistry.
+ //
+ // This is not thread-safe and must only be called on the RenderThread, but
+ // even so, it's not thread safe because other threads may decide to
+ // modify this. Don't persist a reference to this.
+ //
+ // TODO(annekao): remove or make thread-safe and callback-based.
+ const ExtensionSet* GetMainThreadExtensionSet() const;
+
+ size_t size() const;
+ bool is_empty() const;
+
+ // Forwards to the ExtensionSet methods by the same name.
+ bool Contains(const std::string& id) const;
+ bool Insert(const scoped_refptr<const Extension>& extension);
+ bool Remove(const std::string& id);
+ std::string GetExtensionOrAppIDByURL(const GURL& url) const;
+ const Extension* GetExtensionOrAppByURL(const GURL& url) const;
+ const Extension* GetHostedAppByURL(const GURL& url) const;
+ const Extension* GetByID(const std::string& id) const;
+ ExtensionIdSet GetIDs() const;
+ bool ExtensionBindingsAllowed(const GURL& url) const;
+
+ private:
+ ExtensionSet extensions_;
+
+ mutable base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(RendererExtensionRegistry);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_RENDERER_EXTENSION_REGISTRY_H_
diff --git a/chromium/extensions/renderer/request_sender.cc b/chromium/extensions/renderer/request_sender.cc
new file mode 100644
index 00000000000..6f3cf51a8f1
--- /dev/null
+++ b/chromium/extensions/renderer/request_sender.cc
@@ -0,0 +1,143 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/request_sender.h"
+
+#include "base/values.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
+#include "third_party/WebKit/public/web/WebUserGestureToken.h"
+
+namespace extensions {
+
+// Contains info relevant to a pending API request.
+struct PendingRequest {
+ public:
+ PendingRequest(const std::string& name,
+ RequestSender::Source* source,
+ blink::WebUserGestureToken token)
+ : name(name), source(source), token(token) {}
+
+ std::string name;
+ RequestSender::Source* source;
+ blink::WebUserGestureToken token;
+};
+
+RequestSender::ScopedTabID::ScopedTabID(RequestSender* request_sender,
+ int tab_id)
+ : request_sender_(request_sender),
+ tab_id_(tab_id),
+ previous_tab_id_(request_sender->source_tab_id_) {
+ request_sender_->source_tab_id_ = tab_id;
+}
+
+RequestSender::ScopedTabID::~ScopedTabID() {
+ DCHECK_EQ(tab_id_, request_sender_->source_tab_id_);
+ request_sender_->source_tab_id_ = previous_tab_id_;
+}
+
+RequestSender::RequestSender(Dispatcher* dispatcher)
+ : dispatcher_(dispatcher), source_tab_id_(-1) {}
+
+RequestSender::~RequestSender() {}
+
+void RequestSender::InsertRequest(int request_id,
+ PendingRequest* pending_request) {
+ DCHECK_EQ(0u, pending_requests_.count(request_id));
+ pending_requests_[request_id].reset(pending_request);
+}
+
+linked_ptr<PendingRequest> RequestSender::RemoveRequest(int request_id) {
+ PendingRequestMap::iterator i = pending_requests_.find(request_id);
+ if (i == pending_requests_.end())
+ return linked_ptr<PendingRequest>();
+ linked_ptr<PendingRequest> result = i->second;
+ pending_requests_.erase(i);
+ return result;
+}
+
+int RequestSender::GetNextRequestId() const {
+ static int next_request_id = 0;
+ return next_request_id++;
+}
+
+void RequestSender::StartRequest(Source* source,
+ const std::string& name,
+ int request_id,
+ bool has_callback,
+ bool for_io_thread,
+ base::ListValue* value_args) {
+ ScriptContext* context = source->GetContext();
+ if (!context)
+ return;
+
+ // Get the current RenderFrame so that we can send a routed IPC message from
+ // the correct source.
+ content::RenderFrame* render_frame = context->GetRenderFrame();
+ if (!render_frame)
+ return;
+
+ // TODO(koz): See if we can make this a CHECK.
+ if (!context->HasAccessOrThrowError(name))
+ return;
+
+ GURL source_url;
+ if (blink::WebLocalFrame* webframe = context->web_frame())
+ source_url = webframe->document().url();
+
+ InsertRequest(request_id, new PendingRequest(name, source,
+ blink::WebUserGestureIndicator::currentUserGestureToken()));
+
+ ExtensionHostMsg_Request_Params params;
+ params.name = name;
+ params.arguments.Swap(value_args);
+ params.extension_id = context->GetExtensionID();
+ params.source_url = source_url;
+ params.source_tab_id = source_tab_id_;
+ params.request_id = request_id;
+ params.has_callback = has_callback;
+ params.user_gesture =
+ blink::WebUserGestureIndicator::isProcessingUserGesture();
+ if (for_io_thread) {
+ render_frame->Send(new ExtensionHostMsg_RequestForIOThread(
+ render_frame->GetRoutingID(), params));
+ } else {
+ render_frame->Send(
+ new ExtensionHostMsg_Request(render_frame->GetRoutingID(), params));
+ }
+}
+
+void RequestSender::HandleResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) {
+ linked_ptr<PendingRequest> request = RemoveRequest(request_id);
+
+ if (!request.get()) {
+ // This can happen if a context is destroyed while a request is in flight.
+ return;
+ }
+
+ blink::WebScopedUserGesture gesture(request->token);
+ request->source->OnResponseReceived(
+ request->name, request_id, success, response, error);
+}
+
+void RequestSender::InvalidateSource(Source* source) {
+ for (PendingRequestMap::iterator it = pending_requests_.begin();
+ it != pending_requests_.end();) {
+ if (it->second->source == source)
+ pending_requests_.erase(it++);
+ else
+ ++it;
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/request_sender.h b/chromium/extensions/renderer/request_sender.h
new file mode 100644
index 00000000000..245c0feb67e
--- /dev/null
+++ b/chromium/extensions/renderer/request_sender.h
@@ -0,0 +1,107 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_REQUEST_SENDER_H_
+#define EXTENSIONS_RENDERER_REQUEST_SENDER_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "v8/include/v8.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace extensions {
+class Dispatcher;
+class ScriptContext;
+
+struct PendingRequest;
+
+// Responsible for sending requests for named extension API functions to the
+// extension host and routing the responses back to the caller.
+class RequestSender {
+ public:
+ // Source represents a user of RequestSender. Every request is associated with
+ // a Source object, which will be notified when the corresponding response
+ // arrives. When a Source object is going away and there are pending requests,
+ // it should call InvalidateSource() to make sure no notifications are sent to
+ // it later.
+ class Source {
+ public:
+ virtual ~Source() {}
+
+ virtual ScriptContext* GetContext() = 0;
+ virtual void OnResponseReceived(const std::string& name,
+ int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) = 0;
+ };
+
+ // Helper class to (re)set the |source_tab_id_| below.
+ class ScopedTabID {
+ public:
+ ScopedTabID(RequestSender* request_sender, int tab_id);
+ ~ScopedTabID();
+
+ private:
+ RequestSender* const request_sender_;
+ const int tab_id_;
+ const int previous_tab_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedTabID);
+ };
+
+ explicit RequestSender(Dispatcher* dispatcher);
+ ~RequestSender();
+
+ // In order to avoid collision, all |request_id|s passed into StartRequest()
+ // should be generated by this method.
+ int GetNextRequestId() const;
+
+ // Makes a call to the API function |name| that is to be handled by the
+ // extension host. The response to this request will be received in
+ // HandleResponse().
+ // TODO(koz): Remove |request_id| and generate that internally.
+ // There are multiple of these per render view though, so we'll
+ // need to vend the IDs centrally.
+ void StartRequest(Source* source,
+ const std::string& name,
+ int request_id,
+ bool has_callback,
+ bool for_io_thread,
+ base::ListValue* value_args);
+
+ // Handles responses from the extension host to calls made by StartRequest().
+ void HandleResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error);
+
+ // Notifies this that a request source is no longer valid.
+ // TODO(kalman): Do this in a generic/safe way.
+ void InvalidateSource(Source* source);
+
+ private:
+ friend class ScopedTabID;
+ typedef std::map<int, linked_ptr<PendingRequest> > PendingRequestMap;
+
+ void InsertRequest(int request_id, PendingRequest* pending_request);
+ linked_ptr<PendingRequest> RemoveRequest(int request_id);
+
+ Dispatcher* dispatcher_;
+ PendingRequestMap pending_requests_;
+
+ int source_tab_id_; // Id of the tab sending the request, or -1 if no tab.
+
+ DISALLOW_COPY_AND_ASSIGN(RequestSender);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_REQUEST_SENDER_H_
diff --git a/chromium/extensions/renderer/resource_bundle_source_map.cc b/chromium/extensions/renderer/resource_bundle_source_map.cc
new file mode 100644
index 00000000000..88fc5372185
--- /dev/null
+++ b/chromium/extensions/renderer/resource_bundle_source_map.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/resource_bundle_source_map.h"
+
+#include "base/logging.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace extensions {
+
+ResourceBundleSourceMap::ResourceBundleSourceMap(
+ const ui::ResourceBundle* resource_bundle)
+ : resource_bundle_(resource_bundle) {
+}
+
+ResourceBundleSourceMap::~ResourceBundleSourceMap() {
+}
+
+void ResourceBundleSourceMap::RegisterSource(const std::string& name,
+ int resource_id) {
+ resource_id_map_[name] = resource_id;
+}
+
+v8::Local<v8::Value> ResourceBundleSourceMap::GetSource(
+ v8::Isolate* isolate,
+ const std::string& name) {
+ if (!Contains(name)) {
+ NOTREACHED() << "No module is registered with name \"" << name << "\"";
+ return v8::Undefined(isolate);
+ }
+ base::StringPiece resource =
+ resource_bundle_->GetRawDataResource(resource_id_map_[name]);
+ if (resource.empty()) {
+ NOTREACHED()
+ << "Module resource registered as \"" << name << "\" not found";
+ return v8::Undefined(isolate);
+ }
+ return ConvertString(isolate, resource);
+}
+
+bool ResourceBundleSourceMap::Contains(const std::string& name) {
+ return !!resource_id_map_.count(name);
+}
+
+v8::Local<v8::String> ResourceBundleSourceMap::ConvertString(
+ v8::Isolate* isolate,
+ const base::StringPiece& string) {
+ // v8 takes ownership of the StaticV8ExternalOneByteStringResource (see
+ // v8::String::NewExternal()).
+ return v8::String::NewExternal(
+ isolate, new StaticV8ExternalOneByteStringResource(string));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/resource_bundle_source_map.h b/chromium/extensions/renderer/resource_bundle_source_map.h
new file mode 100644
index 00000000000..13f8f216697
--- /dev/null
+++ b/chromium/extensions/renderer/resource_bundle_source_map.h
@@ -0,0 +1,45 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_RESOURCE_BUNDLE_SOURCE_MAP_H_
+#define EXTENSIONS_RENDERER_RESOURCE_BUNDLE_SOURCE_MAP_H_
+
+#include <map>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/linked_ptr.h"
+#include "base/strings/string_piece.h"
+#include "extensions/renderer/module_system.h"
+#include "extensions/renderer/static_v8_external_one_byte_string_resource.h"
+#include "v8/include/v8.h"
+
+namespace ui {
+class ResourceBundle;
+}
+
+namespace extensions {
+
+class ResourceBundleSourceMap : public extensions::ModuleSystem::SourceMap {
+ public:
+ explicit ResourceBundleSourceMap(const ui::ResourceBundle* resource_bundle);
+ ~ResourceBundleSourceMap() override;
+
+ v8::Local<v8::Value> GetSource(v8::Isolate* isolate,
+ const std::string& name) override;
+ bool Contains(const std::string& name) override;
+
+ void RegisterSource(const std::string& name, int resource_id);
+
+ private:
+ v8::Local<v8::String> ConvertString(v8::Isolate* isolate,
+ const base::StringPiece& string);
+
+ const ui::ResourceBundle* resource_bundle_;
+ std::map<std::string, int> resource_id_map_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_RESOURCE_BUNDLE_SOURCE_MAP_H_
diff --git a/chromium/extensions/renderer/resources/OWNERS b/chromium/extensions/renderer/resources/OWNERS
new file mode 100644
index 00000000000..33637287bcf
--- /dev/null
+++ b/chromium/extensions/renderer/resources/OWNERS
@@ -0,0 +1,4 @@
+per-file media_router_bindings.js=imcheng@chromium.org
+per-file media_router_bindings.js=kmarshall@chromium.org
+per-file media_router_bindings.js=mfoltz@chromium.org
+
diff --git a/chromium/extensions/renderer/resources/app_runtime_custom_bindings.js b/chromium/extensions/renderer/resources/app_runtime_custom_bindings.js
new file mode 100644
index 00000000000..3f0dbd27272
--- /dev/null
+++ b/chromium/extensions/renderer/resources/app_runtime_custom_bindings.js
@@ -0,0 +1,83 @@
+// Copyright 2014 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.
+
+// Custom binding for the chrome.app.runtime API.
+
+var binding = require('binding').Binding.create('app.runtime');
+
+var AppViewGuestInternal =
+ require('binding').Binding.create('appViewGuestInternal').generate();
+var eventBindings = require('event_bindings');
+var fileSystemHelpers = requireNative('file_system_natives');
+var GetIsolatedFileSystem = fileSystemHelpers.GetIsolatedFileSystem;
+var entryIdManager = require('entryIdManager');
+
+eventBindings.registerArgumentMassager('app.runtime.onEmbedRequested',
+ function(args, dispatch) {
+ var appEmbeddingRequest = args[0];
+ var id = appEmbeddingRequest.guestInstanceId;
+ delete appEmbeddingRequest.guestInstanceId;
+ appEmbeddingRequest.allow = function(url) {
+ AppViewGuestInternal.attachFrame(url, id);
+ };
+
+ appEmbeddingRequest.deny = function() {
+ AppViewGuestInternal.denyRequest(id);
+ };
+
+ dispatch([appEmbeddingRequest]);
+});
+
+eventBindings.registerArgumentMassager('app.runtime.onLaunched',
+ function(args, dispatch) {
+ var launchData = args[0];
+ if (launchData.items) {
+ // An onLaunched corresponding to file_handlers in the app's manifest.
+ var items = [];
+ var numItems = launchData.items.length;
+ var itemLoaded = function(err, item) {
+ if (err) {
+ console.error('Error getting fileEntry, code: ' + err.code);
+ } else {
+ $Array.push(items, item);
+ }
+ if (--numItems === 0) {
+ var data = {
+ isKioskSession: launchData.isKioskSession,
+ isPublicSession: launchData.isPublicSession,
+ source: launchData.source
+ };
+ if (items.length !== 0) {
+ data.id = launchData.id;
+ data.items = items;
+ }
+ dispatch([data]);
+ }
+ };
+ $Array.forEach(launchData.items, function(item) {
+ var fs = GetIsolatedFileSystem(item.fileSystemId);
+ if (item.isDirectory) {
+ fs.root.getDirectory(item.baseName, {}, function(dirEntry) {
+ entryIdManager.registerEntry(item.entryId, dirEntry);
+ itemLoaded(null, {entry: dirEntry});
+ }, function(fileError) {
+ itemLoaded(fileError);
+ });
+ } else {
+ fs.root.getFile(item.baseName, {}, function(fileEntry) {
+ entryIdManager.registerEntry(item.entryId, fileEntry);
+ itemLoaded(null, {entry: fileEntry, type: item.mimeType});
+ }, function(fileError) {
+ itemLoaded(fileError);
+ });
+ }
+ });
+ } else {
+ // Default case. This currently covers an onLaunched corresponding to
+ // url_handlers in the app's manifest.
+ dispatch([launchData]);
+ }
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/app_window_custom_bindings.js b/chromium/extensions/renderer/resources/app_window_custom_bindings.js
new file mode 100644
index 00000000000..b0ae54f4a7b
--- /dev/null
+++ b/chromium/extensions/renderer/resources/app_window_custom_bindings.js
@@ -0,0 +1,410 @@
+// 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.
+
+// Custom binding for the app_window API.
+
+var appWindowNatives = requireNative('app_window_natives');
+var runtimeNatives = requireNative('runtime');
+var Binding = require('binding').Binding;
+var Event = require('event_bindings').Event;
+var forEach = require('utils').forEach;
+var renderFrameObserverNatives = requireNative('renderFrameObserverNatives');
+
+var appWindowData = null;
+var currentAppWindow = null;
+var currentWindowInternal = null;
+
+var kSetBoundsFunction = 'setBounds';
+var kSetSizeConstraintsFunction = 'setSizeConstraints';
+
+// Bounds class definition.
+var Bounds = function(boundsKey) {
+ privates(this).boundsKey_ = boundsKey;
+};
+Object.defineProperty(Bounds.prototype, 'left', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].left;
+ },
+ set: function(left) {
+ this.setPosition(left, null);
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'top', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].top;
+ },
+ set: function(top) {
+ this.setPosition(null, top);
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'width', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].width;
+ },
+ set: function(width) {
+ this.setSize(width, null);
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'height', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].height;
+ },
+ set: function(height) {
+ this.setSize(null, height);
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'minWidth', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].minWidth;
+ },
+ set: function(minWidth) {
+ updateSizeConstraints(privates(this).boundsKey_, { minWidth: minWidth });
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'maxWidth', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].maxWidth;
+ },
+ set: function(maxWidth) {
+ updateSizeConstraints(privates(this).boundsKey_, { maxWidth: maxWidth });
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'minHeight', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].minHeight;
+ },
+ set: function(minHeight) {
+ updateSizeConstraints(privates(this).boundsKey_, { minHeight: minHeight });
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'maxHeight', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].maxHeight;
+ },
+ set: function(maxHeight) {
+ updateSizeConstraints(privates(this).boundsKey_, { maxHeight: maxHeight });
+ },
+ enumerable: true
+});
+Bounds.prototype.setPosition = function(left, top) {
+ updateBounds(privates(this).boundsKey_, { left: left, top: top });
+};
+Bounds.prototype.setSize = function(width, height) {
+ updateBounds(privates(this).boundsKey_, { width: width, height: height });
+};
+Bounds.prototype.setMinimumSize = function(minWidth, minHeight) {
+ updateSizeConstraints(privates(this).boundsKey_,
+ { minWidth: minWidth, minHeight: minHeight });
+};
+Bounds.prototype.setMaximumSize = function(maxWidth, maxHeight) {
+ updateSizeConstraints(privates(this).boundsKey_,
+ { maxWidth: maxWidth, maxHeight: maxHeight });
+};
+
+var appWindow = Binding.create('app.window');
+appWindow.registerCustomHook(function(bindingsAPI) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+
+ apiFunctions.setCustomCallback('create',
+ function(name, request, callback, windowParams) {
+ var view = null;
+
+ // When window creation fails, |windowParams| will be undefined.
+ if (windowParams && windowParams.frameId) {
+ view = appWindowNatives.GetFrame(
+ windowParams.frameId, true /* notifyBrowser */);
+ }
+
+ if (!view) {
+ // No route to created window. If given a callback, trigger it with an
+ // undefined object.
+ if (callback)
+ callback();
+ return;
+ }
+
+ if (windowParams.existingWindow) {
+ // Not creating a new window, but activating an existing one, so trigger
+ // callback with existing window and don't do anything else.
+ if (callback)
+ callback(view.chrome.app.window.current());
+ return;
+ }
+
+ // Initialize appWindowData in the newly created JS context
+ if (view.chrome.app) {
+ view.chrome.app.window.initializeAppWindow(windowParams);
+ } else {
+ var sandbox_window_message = 'Creating sandboxed window, it doesn\'t ' +
+ 'have access to the chrome.app API.';
+ if (callback) {
+ sandbox_window_message = sandbox_window_message +
+ ' The chrome.app.window.create callback will be called, but ' +
+ 'there will be no object provided for the sandboxed window.';
+ }
+ console.warn(sandbox_window_message);
+ }
+
+ if (callback) {
+ if (!view || !view.chrome.app /* sandboxed window */) {
+ callback(undefined);
+ return;
+ }
+
+ var willCallback =
+ renderFrameObserverNatives.OnDocumentElementCreated(
+ windowParams.frameId,
+ function(success) {
+ if (success) {
+ callback(view.chrome.app.window.current());
+ } else {
+ callback(undefined);
+ }
+ });
+ if (!willCallback) {
+ callback(undefined);
+ }
+ }
+ });
+
+ apiFunctions.setHandleRequest('current', function() {
+ if (!currentAppWindow) {
+ console.error('The JavaScript context calling ' +
+ 'chrome.app.window.current() has no associated AppWindow.');
+ return null;
+ }
+ return currentAppWindow;
+ });
+
+ apiFunctions.setHandleRequest('getAll', function() {
+ var views = runtimeNatives.GetExtensionViews(-1, 'APP_WINDOW');
+ return $Array.map(views, function(win) {
+ return win.chrome.app.window.current();
+ });
+ });
+
+ apiFunctions.setHandleRequest('get', function(id) {
+ var windows = $Array.filter(chrome.app.window.getAll(), function(win) {
+ return win.id == id;
+ });
+ return windows.length > 0 ? windows[0] : null;
+ });
+
+ apiFunctions.setHandleRequest('canSetVisibleOnAllWorkspaces', function() {
+ return /Mac/.test(navigator.platform) || /Linux/.test(navigator.userAgent);
+ });
+
+ // This is an internal function, but needs to be bound into a closure
+ // so the correct JS context is used for global variables such as
+ // currentWindowInternal, appWindowData, etc.
+ apiFunctions.setHandleRequest('initializeAppWindow', function(params) {
+ currentWindowInternal =
+ Binding.create('app.currentWindowInternal').generate();
+ var AppWindow = function() {
+ this.innerBounds = new Bounds('innerBounds');
+ this.outerBounds = new Bounds('outerBounds');
+ };
+ forEach(currentWindowInternal, function(key, value) {
+ // Do not add internal functions that should not appear in the AppWindow
+ // interface. They are called by Bounds mutators.
+ if (key !== kSetBoundsFunction && key !== kSetSizeConstraintsFunction)
+ AppWindow.prototype[key] = value;
+ });
+ AppWindow.prototype.moveTo = $Function.bind(window.moveTo, window);
+ AppWindow.prototype.resizeTo = $Function.bind(window.resizeTo, window);
+ AppWindow.prototype.contentWindow = window;
+ AppWindow.prototype.onClosed = new Event();
+ AppWindow.prototype.onWindowFirstShownForTests = new Event();
+ AppWindow.prototype.close = function() {
+ this.contentWindow.close();
+ };
+ AppWindow.prototype.getBounds = function() {
+ // This is to maintain backcompatibility with a bug on Windows and
+ // ChromeOS, which returns the position of the window but the size of
+ // the content.
+ var innerBounds = appWindowData.innerBounds;
+ var outerBounds = appWindowData.outerBounds;
+ return { left: outerBounds.left, top: outerBounds.top,
+ width: innerBounds.width, height: innerBounds.height };
+ };
+ AppWindow.prototype.setBounds = function(bounds) {
+ updateBounds('bounds', bounds);
+ };
+ AppWindow.prototype.isFullscreen = function() {
+ return appWindowData.fullscreen;
+ };
+ AppWindow.prototype.isMinimized = function() {
+ return appWindowData.minimized;
+ };
+ AppWindow.prototype.isMaximized = function() {
+ return appWindowData.maximized;
+ };
+ AppWindow.prototype.isAlwaysOnTop = function() {
+ return appWindowData.alwaysOnTop;
+ };
+ AppWindow.prototype.alphaEnabled = function() {
+ return appWindowData.alphaEnabled;
+ };
+ AppWindow.prototype.handleWindowFirstShownForTests = function(callback) {
+ // This allows test apps to get have their callback run even if they
+ // call this after the first show has happened.
+ if (this.firstShowHasHappened) {
+ callback();
+ return;
+ }
+ this.onWindowFirstShownForTests.addListener(callback);
+ }
+
+ Object.defineProperty(AppWindow.prototype, 'id', {get: function() {
+ return appWindowData.id;
+ }});
+
+ // These properties are for testing.
+ Object.defineProperty(
+ AppWindow.prototype, 'hasFrameColor', {get: function() {
+ return appWindowData.hasFrameColor;
+ }});
+
+ Object.defineProperty(AppWindow.prototype, 'activeFrameColor',
+ {get: function() {
+ return appWindowData.activeFrameColor;
+ }});
+
+ Object.defineProperty(AppWindow.prototype, 'inactiveFrameColor',
+ {get: function() {
+ return appWindowData.inactiveFrameColor;
+ }});
+
+ appWindowData = {
+ id: params.id || '',
+ innerBounds: {
+ left: params.innerBounds.left,
+ top: params.innerBounds.top,
+ width: params.innerBounds.width,
+ height: params.innerBounds.height,
+
+ minWidth: params.innerBounds.minWidth,
+ minHeight: params.innerBounds.minHeight,
+ maxWidth: params.innerBounds.maxWidth,
+ maxHeight: params.innerBounds.maxHeight
+ },
+ outerBounds: {
+ left: params.outerBounds.left,
+ top: params.outerBounds.top,
+ width: params.outerBounds.width,
+ height: params.outerBounds.height,
+
+ minWidth: params.outerBounds.minWidth,
+ minHeight: params.outerBounds.minHeight,
+ maxWidth: params.outerBounds.maxWidth,
+ maxHeight: params.outerBounds.maxHeight
+ },
+ fullscreen: params.fullscreen,
+ minimized: params.minimized,
+ maximized: params.maximized,
+ alwaysOnTop: params.alwaysOnTop,
+ hasFrameColor: params.hasFrameColor,
+ activeFrameColor: params.activeFrameColor,
+ inactiveFrameColor: params.inactiveFrameColor,
+ alphaEnabled: params.alphaEnabled
+ };
+ currentAppWindow = new AppWindow;
+ });
+});
+
+function boundsEqual(bounds1, bounds2) {
+ if (!bounds1 || !bounds2)
+ return false;
+ return (bounds1.left == bounds2.left && bounds1.top == bounds2.top &&
+ bounds1.width == bounds2.width && bounds1.height == bounds2.height);
+}
+
+function dispatchEventIfExists(target, name) {
+ // Sometimes apps like to put their own properties on the window which
+ // break our assumptions.
+ var event = target[name];
+ if (event && (typeof event.dispatch == 'function'))
+ event.dispatch();
+ else
+ console.warn('Could not dispatch ' + name + ', event has been clobbered');
+}
+
+function updateAppWindowProperties(update) {
+ if (!appWindowData)
+ return;
+
+ var oldData = appWindowData;
+ update.id = oldData.id;
+ appWindowData = update;
+
+ var currentWindow = currentAppWindow;
+
+ if (!boundsEqual(oldData.innerBounds, update.innerBounds))
+ dispatchEventIfExists(currentWindow, "onBoundsChanged");
+
+ if (!oldData.fullscreen && update.fullscreen)
+ dispatchEventIfExists(currentWindow, "onFullscreened");
+ if (!oldData.minimized && update.minimized)
+ dispatchEventIfExists(currentWindow, "onMinimized");
+ if (!oldData.maximized && update.maximized)
+ dispatchEventIfExists(currentWindow, "onMaximized");
+
+ if ((oldData.fullscreen && !update.fullscreen) ||
+ (oldData.minimized && !update.minimized) ||
+ (oldData.maximized && !update.maximized))
+ dispatchEventIfExists(currentWindow, "onRestored");
+
+ if (oldData.alphaEnabled !== update.alphaEnabled)
+ dispatchEventIfExists(currentWindow, "onAlphaEnabledChanged");
+};
+
+function onAppWindowShownForTests() {
+ if (!currentAppWindow)
+ return;
+
+ if (!currentAppWindow.firstShowHasHappened)
+ dispatchEventIfExists(currentAppWindow, "onWindowFirstShownForTests");
+
+ currentAppWindow.firstShowHasHappened = true;
+}
+
+function onAppWindowClosed() {
+ if (!currentAppWindow)
+ return;
+ dispatchEventIfExists(currentAppWindow, "onClosed");
+}
+
+function updateBounds(boundsType, bounds) {
+ if (!currentWindowInternal)
+ return;
+
+ currentWindowInternal.setBounds(boundsType, bounds);
+}
+
+function updateSizeConstraints(boundsType, constraints) {
+ if (!currentWindowInternal)
+ return;
+
+ forEach(constraints, function(key, value) {
+ // From the perspective of the API, null is used to reset constraints.
+ // We need to convert this to 0 because a value of null is interpreted
+ // the same as undefined in the browser and leaves the constraint unchanged.
+ if (value === null)
+ constraints[key] = 0;
+ });
+
+ currentWindowInternal.setSizeConstraints(boundsType, constraints);
+}
+
+exports.$set('binding', appWindow.generate());
+exports.$set('onAppWindowClosed', onAppWindowClosed);
+exports.$set('updateAppWindowProperties', updateAppWindowProperties);
+exports.$set('appWindowShownForTests', onAppWindowShownForTests);
diff --git a/chromium/extensions/renderer/resources/async_waiter.js b/chromium/extensions/renderer/resources/async_waiter.js
new file mode 100644
index 00000000000..6470f64b4d5
--- /dev/null
+++ b/chromium/extensions/renderer/resources/async_waiter.js
@@ -0,0 +1,93 @@
+// Copyright 2014 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.
+
+define('async_waiter', [
+ 'mojo/public/js/support',
+], function(supportModule) {
+ /**
+ * @module async_waiter
+ */
+
+ /**
+ * @callback module:async_waiter.AsyncWaiter.Callback
+ * @param {number} result The result of waiting.
+ */
+
+ /**
+ * A waiter that waits for a handle to be ready for either reading or writing.
+ * @param {!MojoHandle} handle The handle to wait on.
+ * @param {number} signals The signals to wait for handle to be ready for.
+ * @param {module:async_waiter.AsyncWaiter.Callback} callback The callback to
+ * call when handle is ready.
+ * @constructor
+ * @alias module:async_waiter.AsyncWaiter
+ */
+ function AsyncWaiter(handle, signals, callback) {
+ /**
+ * The handle to wait on.
+ * @type {!MojoHandle}
+ * @private
+ */
+ this.handle_ = handle;
+
+ /**
+ * The signals to wait for.
+ * @type {number}
+ * @private
+ */
+ this.signals_ = signals;
+
+ /**
+ * The callback to invoke when
+ * |[handle_]{@link module:async_waiter.AsyncWaiter#handle_}| is ready.
+ * @type {module:async_waiter.AsyncWaiter.Callback}
+ * @private
+ */
+ this.callback_ = callback;
+ this.id_ = null;
+ }
+
+ /**
+ * Start waiting for the handle to be ready.
+ * @throws Will throw if this is already waiting.
+ */
+ AsyncWaiter.prototype.start = function() {
+ if (this.id_)
+ throw new Error('Already started');
+ this.id_ = supportModule.asyncWait(
+ this.handle_, this.signals_, this.onHandleReady_.bind(this));
+ };
+
+ /**
+ * Stop waiting for the handle to be ready.
+ */
+ AsyncWaiter.prototype.stop = function() {
+ if (!this.id_)
+ return;
+
+ supportModule.cancelWait(this.id_);
+ this.id_ = null;
+ };
+
+ /**
+ * Returns whether this {@link AsyncWaiter} is waiting.
+ * @return {boolean} Whether this AsyncWaiter is waiting.
+ */
+ AsyncWaiter.prototype.isWaiting = function() {
+ return !!this.id_;
+ };
+
+ /**
+ * Invoked when |[handle_]{@link module:async_waiter.AsyncWaiter#handle_}| is
+ * ready.
+ * @param {number} result The result of the wait.
+ * @private
+ */
+ AsyncWaiter.prototype.onHandleReady_ = function(result) {
+ this.id_ = null;
+ this.callback_(result);
+ };
+
+ return {AsyncWaiter: AsyncWaiter};
+});
diff --git a/chromium/extensions/renderer/resources/binding.js b/chromium/extensions/renderer/resources/binding.js
new file mode 100644
index 00000000000..c5690907f40
--- /dev/null
+++ b/chromium/extensions/renderer/resources/binding.js
@@ -0,0 +1,574 @@
+// Copyright 2014 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.
+
+var Event = require('event_bindings').Event;
+var forEach = require('utils').forEach;
+// Note: Beware sneaky getters/setters when using GetAvailbility(). Use safe/raw
+// variables as arguments.
+var GetAvailability = requireNative('v8_context').GetAvailability;
+var exceptionHandler = require('uncaught_exception_handler');
+var lastError = require('lastError');
+var logActivity = requireNative('activityLogger');
+var logging = requireNative('logging');
+var process = requireNative('process');
+var schemaRegistry = requireNative('schema_registry');
+var schemaUtils = require('schemaUtils');
+var utils = require('utils');
+var sendRequestHandler = require('sendRequest');
+
+var contextType = process.GetContextType();
+var extensionId = process.GetExtensionId();
+var manifestVersion = process.GetManifestVersion();
+var sendRequest = sendRequestHandler.sendRequest;
+
+// Stores the name and definition of each API function, with methods to
+// modify their behaviour (such as a custom way to handle requests to the
+// API, a custom callback, etc).
+function APIFunctions(namespace) {
+ this.apiFunctions_ = {};
+ this.unavailableApiFunctions_ = {};
+ this.namespace = namespace;
+}
+
+APIFunctions.prototype.register = function(apiName, apiFunction) {
+ this.apiFunctions_[apiName] = apiFunction;
+};
+
+// Registers a function as existing but not available, meaning that calls to
+// the set* methods that reference this function should be ignored rather
+// than throwing Errors.
+APIFunctions.prototype.registerUnavailable = function(apiName) {
+ this.unavailableApiFunctions_[apiName] = apiName;
+};
+
+APIFunctions.prototype.setHook_ =
+ function(apiName, propertyName, customizedFunction) {
+ if ($Object.hasOwnProperty(this.unavailableApiFunctions_, apiName))
+ return;
+ if (!$Object.hasOwnProperty(this.apiFunctions_, apiName))
+ throw new Error('Tried to set hook for unknown API "' + apiName + '"');
+ this.apiFunctions_[apiName][propertyName] = customizedFunction;
+};
+
+APIFunctions.prototype.setHandleRequest =
+ function(apiName, customizedFunction) {
+ var prefix = this.namespace;
+ return this.setHook_(apiName, 'handleRequest',
+ function() {
+ var ret = $Function.apply(customizedFunction, this, arguments);
+ // Logs API calls to the Activity Log if it doesn't go through an
+ // ExtensionFunction.
+ if (!sendRequestHandler.getCalledSendRequest())
+ logActivity.LogAPICall(extensionId, prefix + "." + apiName,
+ $Array.slice(arguments));
+ return ret;
+ });
+};
+
+APIFunctions.prototype.setHandleRequestWithPromise =
+ function(apiName, customizedFunction) {
+ var prefix = this.namespace;
+ return this.setHook_(apiName, 'handleRequest', function() {
+ var name = prefix + '.' + apiName;
+ logActivity.LogAPICall(extensionId, name, $Array.slice(arguments));
+ var stack = exceptionHandler.getExtensionStackTrace();
+ var callback = arguments[arguments.length - 1];
+ var args = $Array.slice(arguments, 0, arguments.length - 1);
+ var keepAlivePromise = requireAsync('keep_alive').then(function(module) {
+ return module.createKeepAlive();
+ });
+ $Function.apply(customizedFunction, this, args).then(function(result) {
+ if (callback) {
+ sendRequestHandler.safeCallbackApply(name, {'stack': stack}, callback,
+ [result]);
+ }
+ }).catch(function(error) {
+ if (callback) {
+ var message = exceptionHandler.safeErrorToString(error, true);
+ lastError.run(name, message, stack, callback);
+ }
+ }).then(function() {
+ keepAlivePromise.then(function(keepAlive) {
+ keepAlive.close();
+ });
+ });
+ });
+};
+
+APIFunctions.prototype.setUpdateArgumentsPostValidate =
+ function(apiName, customizedFunction) {
+ return this.setHook_(
+ apiName, 'updateArgumentsPostValidate', customizedFunction);
+};
+
+APIFunctions.prototype.setUpdateArgumentsPreValidate =
+ function(apiName, customizedFunction) {
+ return this.setHook_(
+ apiName, 'updateArgumentsPreValidate', customizedFunction);
+};
+
+APIFunctions.prototype.setCustomCallback =
+ function(apiName, customizedFunction) {
+ return this.setHook_(apiName, 'customCallback', customizedFunction);
+};
+
+function CustomBindingsObject() {
+}
+
+CustomBindingsObject.prototype.setSchema = function(schema) {
+ // The functions in the schema are in list form, so we move them into a
+ // dictionary for easier access.
+ var self = this;
+ self.functionSchemas = {};
+ $Array.forEach(schema.functions, function(f) {
+ self.functionSchemas[f.name] = {
+ name: f.name,
+ definition: f
+ }
+ });
+};
+
+// Get the platform from navigator.appVersion.
+function getPlatform() {
+ var platforms = [
+ [/CrOS Touch/, "chromeos touch"],
+ [/CrOS/, "chromeos"],
+ [/Linux/, "linux"],
+ [/Mac/, "mac"],
+ [/Win/, "win"],
+ ];
+
+ for (var i = 0; i < platforms.length; i++) {
+ if ($RegExp.exec(platforms[i][0], navigator.appVersion)) {
+ return platforms[i][1];
+ }
+ }
+ return "unknown";
+}
+
+function isPlatformSupported(schemaNode, platform) {
+ return !schemaNode.platforms ||
+ $Array.indexOf(schemaNode.platforms, platform) > -1;
+}
+
+function isManifestVersionSupported(schemaNode, manifestVersion) {
+ return !schemaNode.maximumManifestVersion ||
+ manifestVersion <= schemaNode.maximumManifestVersion;
+}
+
+function isSchemaNodeSupported(schemaNode, platform, manifestVersion) {
+ return isPlatformSupported(schemaNode, platform) &&
+ isManifestVersionSupported(schemaNode, manifestVersion);
+}
+
+function createCustomType(type) {
+ var jsModuleName = type.js_module;
+ logging.CHECK(jsModuleName, 'Custom type ' + type.id +
+ ' has no "js_module" property.');
+ // This list contains all types that has a js_module property. It is ugly to
+ // hard-code them here, but the number of APIs that use js_module has not
+ // changed since the introduction of js_modules in crbug.com/222156.
+ // This whitelist serves as an extra line of defence to avoid exposing
+ // arbitrary extension modules when the |type| definition is poisoned.
+ var whitelistedModules = [
+ 'ChromeDirectSetting',
+ 'ChromeSetting',
+ 'ContentSetting',
+ 'StorageArea',
+ ];
+ logging.CHECK($Array.indexOf(whitelistedModules, jsModuleName) !== -1,
+ 'Module ' + jsModuleName + ' does not define a custom type.');
+ var jsModule = require(jsModuleName);
+ logging.CHECK(jsModule, 'No module ' + jsModuleName + ' found for ' +
+ type.id + '.');
+ var customType = jsModule[jsModuleName];
+ logging.CHECK(customType, jsModuleName + ' must export itself.');
+ customType.prototype = new CustomBindingsObject();
+ customType.prototype.setSchema(type);
+ return customType;
+}
+
+var platform = getPlatform();
+
+function Binding(apiName) {
+ this.apiName_ = apiName;
+ this.apiFunctions_ = new APIFunctions(apiName);
+ this.customEvent_ = null;
+ this.customHooks_ = [];
+};
+
+Binding.create = function(apiName) {
+ return new Binding(apiName);
+};
+
+Binding.prototype = {
+ // The API through which the ${api_name}_custom_bindings.js files customize
+ // their API bindings beyond what can be generated.
+ //
+ // There are 2 types of customizations available: those which are required in
+ // order to do the schema generation (registerCustomEvent and
+ // registerCustomType), and those which can only run after the bindings have
+ // been generated (registerCustomHook).
+
+ // Registers a custom event type for the API identified by |namespace|.
+ // |event| is the event's constructor.
+ registerCustomEvent: function(event) {
+ this.customEvent_ = event;
+ },
+
+ // Registers a function |hook| to run after the schema for all APIs has been
+ // generated. The hook is passed as its first argument an "API" object to
+ // interact with, and second the current extension ID. See where
+ // |customHooks| is used.
+ registerCustomHook: function(fn) {
+ $Array.push(this.customHooks_, fn);
+ },
+
+ // TODO(kalman/cduvall): Refactor this so |runHooks_| is not needed.
+ runHooks_: function(api, schema) {
+ $Array.forEach(this.customHooks_, function(hook) {
+ if (!isSchemaNodeSupported(schema, platform, manifestVersion))
+ return;
+
+ if (!hook)
+ return;
+
+ hook({
+ apiFunctions: this.apiFunctions_,
+ schema: schema,
+ compiledApi: api
+ }, extensionId, contextType);
+ }, this);
+ },
+
+ // Generates the bindings from the schema for |this.apiName_| and integrates
+ // any custom bindings that might be present.
+ generate: function() {
+ // NB: It's important to load the schema during generation rather than
+ // setting it beforehand so that we're more confident the schema we're
+ // loading is real, and not one that was injected by a page intercepting
+ // Binding.generate.
+ // Additionally, since the schema is an object returned from a native
+ // handler, its properties don't have the custom getters/setters that a page
+ // may have put on Object.prototype, and the object is frozen by v8.
+ var schema = schemaRegistry.GetSchema(this.apiName_);
+
+ function shouldCheckUnprivileged() {
+ var shouldCheck = 'unprivileged' in schema;
+ if (shouldCheck)
+ return shouldCheck;
+
+ $Array.forEach(['functions', 'events'], function(type) {
+ if ($Object.hasOwnProperty(schema, type)) {
+ $Array.forEach(schema[type], function(node) {
+ if ('unprivileged' in node)
+ shouldCheck = true;
+ });
+ }
+ });
+ if (shouldCheck)
+ return shouldCheck;
+
+ for (var property in schema.properties) {
+ if ($Object.hasOwnProperty(schema, property) &&
+ 'unprivileged' in schema.properties[property]) {
+ shouldCheck = true;
+ break;
+ }
+ }
+ return shouldCheck;
+ }
+ var checkUnprivileged = shouldCheckUnprivileged();
+
+ // TODO(kalman/cduvall): Make GetAvailability handle this, then delete the
+ // supporting code.
+ if (!isSchemaNodeSupported(schema, platform, manifestVersion)) {
+ console.error('chrome.' + schema.namespace + ' is not supported on ' +
+ 'this platform or manifest version');
+ return undefined;
+ }
+
+ var mod = {};
+
+ var namespaces = $String.split(schema.namespace, '.');
+ for (var index = 0, name; name = namespaces[index]; index++) {
+ mod[name] = mod[name] || {};
+ mod = mod[name];
+ }
+
+ if (schema.types) {
+ $Array.forEach(schema.types, function(t) {
+ if (!isSchemaNodeSupported(t, platform, manifestVersion))
+ return;
+
+ // Add types to global schemaValidator; the types we depend on from
+ // other namespaces will be added as needed.
+ schemaUtils.schemaValidator.addTypes(t);
+
+ // Generate symbols for enums.
+ var enumValues = t['enum'];
+ if (enumValues) {
+ // Type IDs are qualified with the namespace during compilation,
+ // unfortunately, so remove it here.
+ logging.DCHECK($String.substr(t.id, 0, schema.namespace.length) ==
+ schema.namespace);
+ // Note: + 1 because it ends in a '.', e.g., 'fooApi.Type'.
+ var id = $String.substr(t.id, schema.namespace.length + 1);
+ mod[id] = {};
+ $Array.forEach(enumValues, function(enumValue) {
+ // Note: enums can be declared either as a list of strings
+ // ['foo', 'bar'] or as a list of objects
+ // [{'name': 'foo'}, {'name': 'bar'}].
+ enumValue = $Object.hasOwnProperty(enumValue, 'name') ?
+ enumValue.name : enumValue;
+ if (enumValue) { // Avoid setting any empty enums.
+ // Make all properties in ALL_CAPS_STYLE.
+ //
+ // The built-in versions of $String.replace call other built-ins,
+ // which may be clobbered. Instead, manually build the property
+ // name.
+ //
+ // If the first character is a digit (we know it must be one of
+ // a digit, a letter, or an underscore), precede it with an
+ // underscore.
+ var propertyName = ($RegExp.exec(/\d/, enumValue[0])) ? '_' : '';
+ for (var i = 0; i < enumValue.length; ++i) {
+ var next;
+ if (i > 0 && $RegExp.exec(/[a-z]/, enumValue[i-1]) &&
+ $RegExp.exec(/[A-Z]/, enumValue[i])) {
+ // Replace myEnum-Foo with my_Enum-Foo:
+ next = '_' + enumValue[i];
+ } else if ($RegExp.exec(/\W/, enumValue[i])) {
+ // Replace my_Enum-Foo with my_Enum_Foo:
+ next = '_';
+ } else {
+ next = enumValue[i];
+ }
+ propertyName += next;
+ }
+ // Uppercase (replace my_Enum_Foo with MY_ENUM_FOO):
+ propertyName = $String.toUpperCase(propertyName);
+ mod[id][propertyName] = enumValue;
+ }
+ });
+ }
+ }, this);
+ }
+
+ // TODO(cduvall): Take out when all APIs have been converted to features.
+ // Returns whether access to the content of a schema should be denied,
+ // based on the presence of "unprivileged" and whether this is an
+ // extension process (versus e.g. a content script).
+ function isSchemaAccessAllowed(itemSchema) {
+ return (contextType == 'BLESSED_EXTENSION') ||
+ schema.unprivileged ||
+ itemSchema.unprivileged;
+ };
+
+ // Setup Functions.
+ if (schema.functions) {
+ $Array.forEach(schema.functions, function(functionDef) {
+ if (functionDef.name in mod) {
+ throw new Error('Function ' + functionDef.name +
+ ' already defined in ' + schema.namespace);
+ }
+
+ if (!isSchemaNodeSupported(functionDef, platform, manifestVersion)) {
+ this.apiFunctions_.registerUnavailable(functionDef.name);
+ return;
+ }
+
+ var apiFunction = {};
+ apiFunction.definition = functionDef;
+ var apiFunctionName = schema.namespace + '.' + functionDef.name;
+ apiFunction.name = apiFunctionName;
+
+ if (!GetAvailability(apiFunctionName).is_available ||
+ (checkUnprivileged && !isSchemaAccessAllowed(functionDef))) {
+ this.apiFunctions_.registerUnavailable(functionDef.name);
+ return;
+ }
+
+ // TODO(aa): It would be best to run this in a unit test, but in order
+ // to do that we would need to better factor this code so that it
+ // doesn't depend on so much v8::Extension machinery.
+ if (logging.DCHECK_IS_ON() &&
+ schemaUtils.isFunctionSignatureAmbiguous(apiFunction.definition)) {
+ throw new Error(
+ apiFunction.name + ' has ambiguous optional arguments. ' +
+ 'To implement custom disambiguation logic, add ' +
+ '"allowAmbiguousOptionalArguments" to the function\'s schema.');
+ }
+
+ this.apiFunctions_.register(functionDef.name, apiFunction);
+
+ mod[functionDef.name] = $Function.bind(function() {
+ var args = $Array.slice(arguments);
+ if (this.updateArgumentsPreValidate)
+ args = $Function.apply(this.updateArgumentsPreValidate, this, args);
+
+ args = schemaUtils.normalizeArgumentsAndValidate(args, this);
+ if (this.updateArgumentsPostValidate) {
+ args = $Function.apply(this.updateArgumentsPostValidate,
+ this,
+ args);
+ }
+
+ sendRequestHandler.clearCalledSendRequest();
+
+ var retval;
+ if (this.handleRequest) {
+ retval = $Function.apply(this.handleRequest, this, args);
+ } else {
+ var optArgs = {
+ customCallback: this.customCallback
+ };
+ retval = sendRequest(this.name, args,
+ this.definition.parameters,
+ optArgs);
+ }
+ sendRequestHandler.clearCalledSendRequest();
+
+ // Validate return value if in sanity check mode.
+ if (logging.DCHECK_IS_ON() && this.definition.returns)
+ schemaUtils.validate([retval], [this.definition.returns]);
+ return retval;
+ }, apiFunction);
+ }, this);
+ }
+
+ // Setup Events
+ if (schema.events) {
+ $Array.forEach(schema.events, function(eventDef) {
+ if (eventDef.name in mod) {
+ throw new Error('Event ' + eventDef.name +
+ ' already defined in ' + schema.namespace);
+ }
+ if (!isSchemaNodeSupported(eventDef, platform, manifestVersion))
+ return;
+
+ var eventName = schema.namespace + "." + eventDef.name;
+ if (!GetAvailability(eventName).is_available ||
+ (checkUnprivileged && !isSchemaAccessAllowed(eventDef))) {
+ return;
+ }
+
+ var options = eventDef.options || {};
+ if (eventDef.filters && eventDef.filters.length > 0)
+ options.supportsFilters = true;
+
+ var parameters = eventDef.parameters;
+ if (this.customEvent_) {
+ mod[eventDef.name] = new this.customEvent_(
+ eventName, parameters, eventDef.extraParameters, options);
+ } else {
+ mod[eventDef.name] = new Event(eventName, parameters, options);
+ }
+ }, this);
+ }
+
+ function addProperties(m, parentDef) {
+ var properties = parentDef.properties;
+ if (!properties)
+ return;
+
+ forEach(properties, function(propertyName, propertyDef) {
+ if (propertyName in m)
+ return; // TODO(kalman): be strict like functions/events somehow.
+ if (!isSchemaNodeSupported(propertyDef, platform, manifestVersion))
+ return;
+ if (!GetAvailability(schema.namespace + "." +
+ propertyName).is_available ||
+ (checkUnprivileged && !isSchemaAccessAllowed(propertyDef))) {
+ return;
+ }
+
+ // |value| is eventually added to |m|, the exposed API. Make copies
+ // of everything from the schema. (The schema is also frozen, so as long
+ // as we don't make any modifications, shallow copies are fine.)
+ var value;
+ if ($Array.isArray(propertyDef.value))
+ value = $Array.slice(propertyDef.value);
+ else if (typeof propertyDef.value === 'object')
+ value = $Object.assign({}, propertyDef.value);
+ else
+ value = propertyDef.value;
+
+ if (value) {
+ // Values may just have raw types as defined in the JSON, such
+ // as "WINDOW_ID_NONE": { "value": -1 }. We handle this here.
+ // TODO(kalman): enforce that things with a "value" property can't
+ // define their own types.
+ var type = propertyDef.type || typeof(value);
+ if (type === 'integer' || type === 'number') {
+ value = parseInt(value);
+ } else if (type === 'boolean') {
+ value = value === 'true';
+ } else if (propertyDef['$ref']) {
+ var ref = propertyDef['$ref'];
+ var type = utils.loadTypeSchema(propertyDef['$ref'], schema);
+ logging.CHECK(type, 'Schema for $ref type ' + ref + ' not found');
+ var constructor = createCustomType(type);
+ var args = value;
+ // For an object propertyDef, |value| is an array of constructor
+ // arguments, but we want to pass the arguments directly (i.e.
+ // not as an array), so we have to fake calling |new| on the
+ // constructor.
+ value = { __proto__: constructor.prototype };
+ $Function.apply(constructor, value, args);
+ // Recursively add properties.
+ addProperties(value, propertyDef);
+ } else if (type === 'object') {
+ // Recursively add properties.
+ addProperties(value, propertyDef);
+ } else if (type !== 'string') {
+ throw new Error('NOT IMPLEMENTED (extension_api.json error): ' +
+ 'Cannot parse values for type "' + type + '"');
+ }
+ m[propertyName] = value;
+ }
+ });
+ };
+
+ addProperties(mod, schema);
+
+ // This generate() call is considered successful if any functions,
+ // properties, or events were created.
+ var success = ($Object.keys(mod).length > 0);
+
+ // Special case: webViewRequest is a vacuous API which just copies its
+ // implementation from declarativeWebRequest.
+ //
+ // TODO(kalman): This would be unnecessary if we did these checks after the
+ // hooks (i.e. this.runHooks_(mod)). The reason we don't is to be very
+ // conservative with running any JS which might actually be for an API
+ // which isn't available, but this is probably overly cautious given the
+ // C++ is only giving us APIs which are available. FIXME.
+ if (schema.namespace == 'webViewRequest') {
+ success = true;
+ }
+
+ // Special case: runtime.lastError is only occasionally set, so
+ // specifically check its availability.
+ if (schema.namespace == 'runtime' &&
+ GetAvailability('runtime.lastError').is_available) {
+ success = true;
+ }
+
+ if (!success) {
+ var availability = GetAvailability(schema.namespace);
+ // If an API was available it should have been successfully generated.
+ logging.DCHECK(!availability.is_available,
+ schema.namespace + ' was available but not generated');
+ console.error('chrome.' + schema.namespace + ' is not available: ' +
+ availability.message);
+ return;
+ }
+
+ this.runHooks_(mod, schema);
+ return mod;
+ }
+};
+
+exports.$set('Binding', Binding);
diff --git a/chromium/extensions/renderer/resources/browser_test_environment_specific_bindings.js b/chromium/extensions/renderer/resources/browser_test_environment_specific_bindings.js
new file mode 100644
index 00000000000..8574068656d
--- /dev/null
+++ b/chromium/extensions/renderer/resources/browser_test_environment_specific_bindings.js
@@ -0,0 +1,15 @@
+// Copyright 2014 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.
+
+function registerHooks(api) {
+}
+
+function testDone(runNextTest) {
+ // Use setTimeout here to allow previous test contexts to be
+ // eligible for garbage collection.
+ setTimeout(runNextTest, 0);
+}
+
+exports.$set('registerHooks', registerHooks);
+exports.$set('testDone', testDone);
diff --git a/chromium/extensions/renderer/resources/context_menus_custom_bindings.js b/chromium/extensions/renderer/resources/context_menus_custom_bindings.js
new file mode 100644
index 00000000000..ec8080f5294
--- /dev/null
+++ b/chromium/extensions/renderer/resources/context_menus_custom_bindings.js
@@ -0,0 +1,26 @@
+// Copyright 2014 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.
+
+// Custom binding for the contextMenus API.
+
+var binding = require('binding').Binding.create('contextMenus');
+var contextMenusHandlers = require('contextMenusHandlers');
+
+binding.registerCustomHook(function(bindingsAPI) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+
+ var handlers = contextMenusHandlers.create(false /* isWebview */);
+
+ apiFunctions.setHandleRequest('create', handlers.requestHandlers.create);
+
+ apiFunctions.setCustomCallback('create', handlers.callbacks.create);
+
+ apiFunctions.setCustomCallback('remove', handlers.callbacks.remove);
+
+ apiFunctions.setCustomCallback('update', handlers.callbacks.update);
+
+ apiFunctions.setCustomCallback('removeAll', handlers.callbacks.removeAll);
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/context_menus_handlers.js b/chromium/extensions/renderer/resources/context_menus_handlers.js
new file mode 100644
index 00000000000..aef6889d7a3
--- /dev/null
+++ b/chromium/extensions/renderer/resources/context_menus_handlers.js
@@ -0,0 +1,141 @@
+// Copyright 2015 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.
+
+// Implementation of custom bindings for the contextMenus API.
+// This is used to implement the contextMenus API for extensions and for the
+// <webview> tag (see chrome_web_view_experimental.js).
+
+var contextMenuNatives = requireNative('context_menus');
+var sendRequest = require('sendRequest').sendRequest;
+var Event = require('event_bindings').Event;
+var lastError = require('lastError');
+
+// Add the bindings to the contextMenus API.
+function createContextMenusHandlers(isWebview) {
+ var eventName = isWebview ? 'webViewInternal.contextMenus' : 'contextMenus';
+ // Some dummy value for chrome.contextMenus instances.
+ // Webviews use positive integers, and 0 to denote an invalid webview ID.
+ // The following constant is -1 to avoid any conflicts between webview IDs and
+ // extensions.
+ var INSTANCEID_NON_WEBVIEW = -1;
+
+ // Generates a customCallback for a given method. |handleCallback| will be
+ // invoked with |request.args| as parameters.
+ function createCustomCallback(handleCallback) {
+ return function(name, request, callback) {
+ if (lastError.hasError(chrome)) {
+ if (callback)
+ callback();
+ return;
+ }
+ var args = request.args;
+ if (!isWebview) {
+ // <webview>s have an extra item in front of the parameter list, which
+ // specifies the viewInstanceId of the webview. This is used to hide
+ // context menu events in one webview from another.
+ // The non-webview chrome.contextMenus API is not called with such an
+ // ID, so we prepend an ID to match the function signature.
+ args = $Array.concat([INSTANCEID_NON_WEBVIEW], args);
+ }
+ $Function.apply(handleCallback, null, args);
+ if (callback)
+ callback();
+ };
+ }
+
+ var contextMenus = {};
+ contextMenus.handlers = {};
+ contextMenus.event = new Event(eventName);
+
+ contextMenus.getIdFromCreateProperties = function(createProperties) {
+ if (typeof createProperties.id !== 'undefined')
+ return createProperties.id;
+ return createProperties.generatedId;
+ };
+
+ contextMenus.handlersForId = function(instanceId, id) {
+ if (!contextMenus.handlers[instanceId]) {
+ contextMenus.handlers[instanceId] = {
+ generated: {},
+ string: {}
+ };
+ }
+ if (typeof id === 'number')
+ return contextMenus.handlers[instanceId].generated;
+ return contextMenus.handlers[instanceId].string;
+ };
+
+ contextMenus.ensureListenerSetup = function() {
+ if (contextMenus.listening) {
+ return;
+ }
+ contextMenus.listening = true;
+ contextMenus.event.addListener(function(info) {
+ var instanceId = INSTANCEID_NON_WEBVIEW;
+ if (isWebview) {
+ instanceId = info.webviewInstanceId;
+ // Don't expose |webviewInstanceId| via the public API.
+ delete info.webviewInstanceId;
+ }
+
+ var id = info.menuItemId;
+ var onclick = contextMenus.handlersForId(instanceId, id)[id];
+ if (onclick) {
+ $Function.apply(onclick, null, arguments);
+ }
+ });
+ };
+
+ // To be used with apiFunctions.setHandleRequest
+ var requestHandlers = {};
+ // To be used with apiFunctions.setCustomCallback
+ var callbacks = {};
+
+ requestHandlers.create = function() {
+ var createProperties = isWebview ? arguments[1] : arguments[0];
+ createProperties.generatedId = contextMenuNatives.GetNextContextMenuId();
+ var optArgs = {
+ customCallback: this.customCallback,
+ };
+ sendRequest(this.name, arguments, this.definition.parameters, optArgs);
+ return contextMenus.getIdFromCreateProperties(createProperties);
+ };
+
+ callbacks.create =
+ createCustomCallback(function(instanceId, createProperties) {
+ var id = contextMenus.getIdFromCreateProperties(createProperties);
+ var onclick = createProperties.onclick;
+ if (onclick) {
+ contextMenus.ensureListenerSetup();
+ contextMenus.handlersForId(instanceId, id)[id] = onclick;
+ }
+ });
+
+ callbacks.remove = createCustomCallback(function(instanceId, id) {
+ delete contextMenus.handlersForId(instanceId, id)[id];
+ });
+
+ callbacks.update =
+ createCustomCallback(function(instanceId, id, updateProperties) {
+ var onclick = updateProperties.onclick;
+ if (onclick) {
+ contextMenus.ensureListenerSetup();
+ contextMenus.handlersForId(instanceId, id)[id] = onclick;
+ } else if (onclick === null) {
+ // When onclick is explicitly set to null, remove the event listener.
+ delete contextMenus.handlersForId(instanceId, id)[id];
+ }
+ });
+
+ callbacks.removeAll = createCustomCallback(function(instanceId) {
+ delete contextMenus.handlers[instanceId];
+ });
+
+ return {
+ requestHandlers: requestHandlers,
+ callbacks: callbacks
+ };
+}
+
+exports.$set('create', createContextMenusHandlers);
diff --git a/chromium/extensions/renderer/resources/data_receiver.js b/chromium/extensions/renderer/resources/data_receiver.js
new file mode 100644
index 00000000000..a248ffbf4be
--- /dev/null
+++ b/chromium/extensions/renderer/resources/data_receiver.js
@@ -0,0 +1,336 @@
+// Copyright 2014 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.
+
+define('data_receiver', [
+ 'device/serial/data_stream.mojom',
+ 'device/serial/data_stream_serialization.mojom',
+ 'mojo/public/js/core',
+ 'mojo/public/js/router',
+], function(dataStream, serialization, core, router) {
+ /**
+ * @module data_receiver
+ */
+
+ /**
+ * A pending receive operation.
+ * @constructor
+ * @alias module:data_receiver~PendingReceive
+ * @private
+ */
+ function PendingReceive() {
+ /**
+ * The promise that will be resolved or rejected when this receive completes
+ * or fails, respectively.
+ * @type {!Promise<ArrayBuffer>}
+ * @private
+ */
+ this.promise_ = new Promise(function(resolve, reject) {
+ /**
+ * The callback to call with the data received on success.
+ * @type {Function}
+ * @private
+ */
+ this.dataCallback_ = resolve;
+ /**
+ * The callback to call with the error on failure.
+ * @type {Function}
+ * @private
+ */
+ this.errorCallback_ = reject;
+ }.bind(this));
+ }
+
+ /**
+ * Returns the promise that will be resolved when this operation completes or
+ * rejected if an error occurs.
+ * @return {Promise<ArrayBuffer>} A promise to the data received.
+ */
+ PendingReceive.prototype.getPromise = function() {
+ return this.promise_;
+ };
+
+ /**
+ * Dispatches received data to the promise returned by
+ * [getPromise]{@link module:data_receiver.PendingReceive#getPromise}.
+ * @param {!ArrayBuffer} data The data to dispatch.
+ */
+ PendingReceive.prototype.dispatchData = function(data) {
+ this.dataCallback_(data);
+ };
+
+ /**
+ * Dispatches an error if the offset of the error has been reached.
+ * @param {!PendingReceiveError} error The error to dispatch.
+ * @param {number} bytesReceived The number of bytes that have been received.
+ */
+ PendingReceive.prototype.dispatchError = function(error) {
+ if (error.queuePosition > 0)
+ return false;
+
+ var e = new Error();
+ e.error = error.error;
+ this.errorCallback_(e);
+ return true;
+ };
+
+ /**
+ * Unconditionally dispatches an error.
+ * @param {number} error The error to dispatch.
+ */
+ PendingReceive.prototype.dispatchFatalError = function(error) {
+ var e = new Error();
+ e.error = error;
+ this.errorCallback_(e);
+ };
+
+ /**
+ * A DataReceiver that receives data from a DataSource.
+ * @param {!MojoHandle} source The handle to the DataSource.
+ * @param {!MojoHandle} client The handle to the DataSourceClient.
+ * @param {number} bufferSize How large a buffer to use.
+ * @param {number} fatalErrorValue The receive error value to report in the
+ * event of a fatal error.
+ * @constructor
+ * @alias module:data_receiver.DataReceiver
+ */
+ function DataReceiver(source, client, bufferSize, fatalErrorValue) {
+ this.init_(source, client, fatalErrorValue, 0, null, [], false);
+ this.source_.init(bufferSize);
+ }
+
+ DataReceiver.prototype =
+ $Object.create(dataStream.DataSourceClient.stubClass.prototype);
+
+ /**
+ * Closes this DataReceiver.
+ */
+ DataReceiver.prototype.close = function() {
+ if (this.shutDown_)
+ return;
+ this.shutDown_ = true;
+ this.router_.close();
+ this.clientRouter_.close();
+ if (this.receive_) {
+ this.receive_.dispatchFatalError(this.fatalErrorValue_);
+ this.receive_ = null;
+ }
+ };
+
+ /**
+ * Initialize this DataReceiver.
+ * @param {!MojoHandle} source A handle to the DataSource.
+ * @param {!MojoHandle} client A handle to the DataSourceClient.
+ * @param {number} fatalErrorValue The error to dispatch in the event of a
+ * fatal error.
+ * @param {number} bytesReceived The number of bytes already received.
+ * @param {PendingReceiveError} pendingError The pending error if there is
+ * one.
+ * @param {!Array<!ArrayBuffer>} pendingData Data received from the
+ * DataSource not yet requested by the client.
+ * @param {boolean} paused Whether the DataSource is paused.
+ * @private
+ */
+ DataReceiver.prototype.init_ = function(source, client, fatalErrorValue,
+ bytesReceived, pendingError,
+ pendingData, paused) {
+ /**
+ * The [Router]{@link module:mojo/public/js/router.Router} for the
+ * connection to the DataSource.
+ * @private
+ */
+ this.router_ = new router.Router(source);
+ /**
+ * The [Router]{@link module:mojo/public/js/router.Router} for the
+ * connection to the DataSource.
+ * @private
+ */
+ this.clientRouter_ = new router.Router(client);
+ /**
+ * The connection to the DataSource.
+ * @private
+ */
+ this.source_ = new dataStream.DataSource.proxyClass(this.router_);
+ this.client_ = new dataStream.DataSourceClient.stubClass(this);
+ this.clientRouter_.setIncomingReceiver(this.client_);
+ /**
+ * The current receive operation.
+ * @type {module:data_receiver~PendingReceive}
+ * @private
+ */
+ this.receive_ = null;
+ /**
+ * The error to be dispatched in the event of a fatal error.
+ * @const {number}
+ * @private
+ */
+ this.fatalErrorValue_ = fatalErrorValue;
+ /**
+ * The pending error if there is one.
+ * @type {PendingReceiveError}
+ * @private
+ */
+ this.pendingError_ = pendingError;
+ /**
+ * Whether the DataSource is paused.
+ * @type {boolean}
+ * @private
+ */
+ this.paused_ = paused;
+ /**
+ * A queue of data that has been received from the DataSource, but not
+ * consumed by the client.
+ * @type {module:data_receiver~PendingData[]}
+ * @private
+ */
+ this.pendingDataBuffers_ = pendingData;
+ /**
+ * Whether this DataReceiver has shut down.
+ * @type {boolean}
+ * @private
+ */
+ this.shutDown_ = false;
+ };
+
+ /**
+ * Serializes this DataReceiver.
+ * This will cancel a receive if one is in progress.
+ * @return {!Promise<SerializedDataReceiver>} A promise that will resolve to
+ * the serialization of this DataReceiver. If this DataReceiver has shut
+ * down, the promise will resolve to null.
+ */
+ DataReceiver.prototype.serialize = function() {
+ if (this.shutDown_)
+ return Promise.resolve(null);
+
+ if (this.receive_) {
+ this.receive_.dispatchFatalError(this.fatalErrorValue_);
+ this.receive_ = null;
+ }
+ var serialized = new serialization.SerializedDataReceiver();
+ serialized.source = this.router_.connector_.handle_;
+ serialized.client = this.clientRouter_.connector_.handle_;
+ serialized.fatal_error_value = this.fatalErrorValue_;
+ serialized.paused = this.paused_;
+ serialized.pending_error = this.pendingError_;
+ serialized.pending_data = [];
+ $Array.forEach(this.pendingDataBuffers_, function(buffer) {
+ serialized.pending_data.push(new Uint8Array(buffer));
+ });
+ this.router_.connector_.handle_ = null;
+ this.router_.close();
+ this.clientRouter_.connector_.handle_ = null;
+ this.clientRouter_.close();
+ this.shutDown_ = true;
+ return Promise.resolve(serialized);
+ };
+
+ /**
+ * Deserializes a SerializedDataReceiver.
+ * @param {SerializedDataReceiver} serialized The serialized DataReceiver.
+ * @return {!DataReceiver} The deserialized DataReceiver.
+ */
+ DataReceiver.deserialize = function(serialized) {
+ var receiver = $Object.create(DataReceiver.prototype);
+ receiver.deserialize_(serialized);
+ return receiver;
+ };
+
+ /**
+ * Deserializes a SerializedDataReceiver into this DataReceiver.
+ * @param {SerializedDataReceiver} serialized The serialized DataReceiver.
+ * @private
+ */
+ DataReceiver.prototype.deserialize_ = function(serialized) {
+ if (!serialized) {
+ this.shutDown_ = true;
+ return;
+ }
+ var pendingData = [];
+ $Array.forEach(serialized.pending_data, function(data) {
+ var buffer = new Uint8Array(data.length);
+ buffer.set(data);
+ pendingData.push(buffer.buffer);
+ });
+ this.init_(serialized.source, serialized.client,
+ serialized.fatal_error_value, serialized.bytes_received,
+ serialized.pending_error, pendingData, serialized.paused);
+ };
+
+ /**
+ * Receive data from the DataSource.
+ * @return {Promise<ArrayBuffer>} A promise to the received data. If an error
+ * occurs, the promise will reject with an Error object with a property
+ * error containing the error code.
+ * @throws Will throw if this has encountered a fatal error or another receive
+ * is in progress.
+ */
+ DataReceiver.prototype.receive = function() {
+ if (this.shutDown_)
+ throw new Error('DataReceiver has been closed');
+ if (this.receive_)
+ throw new Error('Receive already in progress.');
+ var receive = new PendingReceive();
+ var promise = receive.getPromise();
+ if (this.pendingError_ &&
+ receive.dispatchError(this.pendingError_)) {
+ this.pendingError_ = null;
+ this.paused_ = true;
+ return promise;
+ }
+ if (this.paused_) {
+ this.source_.resume();
+ this.paused_ = false;
+ }
+ this.receive_ = receive;
+ this.dispatchData_();
+ return promise;
+ };
+
+ DataReceiver.prototype.dispatchData_ = function() {
+ if (!this.receive_) {
+ this.close();
+ return;
+ }
+ if (this.pendingDataBuffers_.length) {
+ this.receive_.dispatchData(this.pendingDataBuffers_[0]);
+ this.source_.reportBytesReceived(this.pendingDataBuffers_[0].byteLength);
+ this.receive_ = null;
+ this.pendingDataBuffers_.shift();
+ if (this.pendingError_)
+ this.pendingError_.queuePosition--;
+ }
+ };
+
+ /**
+ * Invoked by the DataSource when an error is encountered.
+ * @param {number} offset The location at which the error occurred.
+ * @param {number} error The error that occurred.
+ * @private
+ */
+ DataReceiver.prototype.onError = function(error) {
+ if (this.shutDown_)
+ return;
+
+ var pendingError = new serialization.PendingReceiveError();
+ pendingError.error = error;
+ pendingError.queuePosition = this.pendingDataBuffers_.length;
+ if (this.receive_ && this.receive_.dispatchError(pendingError)) {
+ this.receive_ = null;
+ this.paused_ = true;
+ return;
+ }
+ this.pendingError_ = pendingError;
+ };
+
+ DataReceiver.prototype.onData = function(data) {
+ var buffer = new ArrayBuffer(data.length);
+ var uintView = new Uint8Array(buffer);
+ uintView.set(data);
+ this.pendingDataBuffers_.push(buffer);
+ if (this.receive_)
+ this.dispatchData_();
+ };
+
+ return {DataReceiver: DataReceiver};
+});
diff --git a/chromium/extensions/renderer/resources/data_sender.js b/chromium/extensions/renderer/resources/data_sender.js
new file mode 100644
index 00000000000..5fa8708ba33
--- /dev/null
+++ b/chromium/extensions/renderer/resources/data_sender.js
@@ -0,0 +1,335 @@
+// Copyright 2014 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.
+
+define('data_sender', [
+ 'device/serial/data_stream.mojom',
+ 'device/serial/data_stream_serialization.mojom',
+ 'mojo/public/js/core',
+ 'mojo/public/js/router',
+], function(dataStreamMojom, serialization, core, routerModule) {
+ /**
+ * @module data_sender
+ */
+
+ /**
+ * A pending send operation.
+ * @param {!ArrayBuffer} data The data to be sent.
+ * @constructor
+ * @alias module:data_sender~PendingSend
+ * @private
+ */
+ function PendingSend(data) {
+ /**
+ * The data to be sent.
+ * @type {ArrayBuffer}
+ * @private
+ */
+ this.data_ = data;
+ /**
+ * The total length of data to be sent.
+ * @type {number}
+ * @private
+ */
+ this.length_ = data.byteLength;
+ /**
+ * The promise that will be resolved or rejected when this send completes
+ * or fails, respectively.
+ * @type {!Promise<number>}
+ * @private
+ */
+ this.promise_ = new Promise(function(resolve, reject) {
+ /**
+ * The callback to call on success.
+ * @type {Function}
+ * @private
+ */
+ this.successCallback_ = resolve;
+ /**
+ * The callback to call with the error on failure.
+ * @type {Function}
+ * @private
+ */
+ this.errorCallback_ = reject;
+ }.bind(this));
+ }
+
+ /**
+ * Returns the promise that will be resolved when this operation completes or
+ * rejected if an error occurs.
+ * @return {!Promise<number>} A promise to the number of bytes sent.
+ */
+ PendingSend.prototype.getPromise = function() {
+ return this.promise_;
+ };
+
+ /**
+ * Invoked when the DataSink reports that bytes have been sent. Resolves the
+ * promise returned by
+ * [getPromise()]{@link module:data_sender~PendingSend#getPromise} once all
+ * bytes have been reported as sent.
+ */
+ PendingSend.prototype.reportBytesSent = function() {
+ this.successCallback_(this.length_);
+ };
+
+ /**
+ * Invoked when the DataSink reports an error. Rejects the promise returned by
+ * [getPromise()]{@link module:data_sender~PendingSend#getPromise} unless the
+ * error occurred after this send, that is, unless numBytes is greater than
+ * the nubmer of outstanding bytes.
+ * @param {number} numBytes The number of bytes sent.
+ * @param {number} error The error reported by the DataSink.
+ */
+ PendingSend.prototype.reportBytesSentAndError = function(numBytes, error) {
+ var e = new Error();
+ e.error = error;
+ e.bytesSent = numBytes;
+ this.errorCallback_(e);
+ };
+
+ /**
+ * Writes pending data into the data pipe.
+ * @param {!DataSink} sink The DataSink to receive the data.
+ * @return {!Object} result The send result.
+ * @return {boolean} result.completed Whether all of the pending data was
+ * sent.
+ */
+ PendingSend.prototype.sendData = function(sink) {
+ var dataSent = sink.onData(new Uint8Array(this.data_));
+ this.data_ = null;
+ return dataSent;
+ };
+
+ /**
+ * A DataSender that sends data to a DataSink.
+ * @param {!MojoHandle} sink The handle to the DataSink.
+ * @param {number} bufferSize How large a buffer to use for data.
+ * @param {number} fatalErrorValue The send error value to report in the
+ * event of a fatal error.
+ * @constructor
+ * @alias module:data_sender.DataSender
+ */
+ function DataSender(sink, bufferSize, fatalErrorValue) {
+ this.init_(sink, fatalErrorValue);
+ }
+
+ /**
+ * Closes this DataSender.
+ */
+ DataSender.prototype.close = function() {
+ if (this.shutDown_)
+ return;
+ this.shutDown_ = true;
+ this.router_.close();
+ while (this.sendsAwaitingAck_.length) {
+ this.sendsAwaitingAck_.pop().reportBytesSentAndError(
+ 0, this.fatalErrorValue_);
+ }
+ this.callCancelCallback_();
+ };
+
+ /**
+ * Initialize this DataSender.
+ * @param {!MojoHandle} sink A handle to the DataSink.
+ * @param {number} fatalErrorValue The error to dispatch in the event of a
+ * fatal error.
+ * @private
+ */
+ DataSender.prototype.init_ = function(sink, fatalErrorValue) {
+ /**
+ * The error to be dispatched in the event of a fatal error.
+ * @const {number}
+ * @private
+ */
+ this.fatalErrorValue_ = fatalErrorValue;
+ /**
+ * Whether this DataSender has shut down.
+ * @type {boolean}
+ * @private
+ */
+ this.shutDown_ = false;
+ /**
+ * The [Router]{@link module:mojo/public/js/router.Router} for the
+ * connection to the DataSink.
+ * @private
+ */
+ this.router_ = new routerModule.Router(sink);
+ /**
+ * The connection to the DataSink.
+ * @private
+ */
+ this.sink_ = new dataStreamMojom.DataSink.proxyClass(this.router_);
+ /**
+ * A queue of sends that have sent their data to the DataSink, but have not
+ * been received by the DataSink.
+ * @type {!module:data_sender~PendingSend[]}
+ * @private
+ */
+ this.sendsAwaitingAck_ = [];
+
+ /**
+ * The callback that will resolve a pending cancel if one is in progress.
+ * @type {?Function}
+ * @private
+ */
+ this.pendingCancel_ = null;
+
+ /**
+ * The promise that will be resolved when a pending cancel completes if one
+ * is in progress.
+ * @type {Promise}
+ * @private
+ */
+ this.cancelPromise_ = null;
+ };
+
+ /**
+ * Serializes this DataSender.
+ * This will cancel any sends in progress before the returned promise
+ * resolves.
+ * @return {!Promise<SerializedDataSender>} A promise that will resolve to
+ * the serialization of this DataSender. If this DataSender has shut down,
+ * the promise will resolve to null.
+ */
+ DataSender.prototype.serialize = function() {
+ if (this.shutDown_)
+ return Promise.resolve(null);
+
+ var readyToSerialize = Promise.resolve();
+ if (this.sendsAwaitingAck_.length) {
+ if (this.pendingCancel_)
+ readyToSerialize = this.cancelPromise_;
+ else
+ readyToSerialize = this.cancel(this.fatalErrorValue_);
+ }
+ return readyToSerialize.then(function() {
+ var serialized = new serialization.SerializedDataSender();
+ serialized.sink = this.router_.connector_.handle_;
+ serialized.fatal_error_value = this.fatalErrorValue_;
+ this.router_.connector_.handle_ = null;
+ this.router_.close();
+ this.shutDown_ = true;
+ return serialized;
+ }.bind(this));
+ };
+
+ /**
+ * Deserializes a SerializedDataSender.
+ * @param {SerializedDataSender} serialized The serialized DataSender.
+ * @return {!DataSender} The deserialized DataSender.
+ */
+ DataSender.deserialize = function(serialized) {
+ var sender = $Object.create(DataSender.prototype);
+ sender.deserialize_(serialized);
+ return sender;
+ };
+
+ /**
+ * Deserializes a SerializedDataSender into this DataSender.
+ * @param {SerializedDataSender} serialized The serialized DataSender.
+ * @private
+ */
+ DataSender.prototype.deserialize_ = function(serialized) {
+ if (!serialized) {
+ this.shutDown_ = true;
+ return;
+ }
+ this.init_(serialized.sink, serialized.fatal_error_value,
+ serialized.buffer_size);
+ };
+
+ /**
+ * Sends data to the DataSink.
+ * @return {!Promise<number>} A promise to the number of bytes sent. If an
+ * error occurs, the promise will reject with an Error object with a
+ * property error containing the error code.
+ * @throws Will throw if this has encountered a fatal error or a cancel is in
+ * progress.
+ */
+ DataSender.prototype.send = function(data) {
+ if (this.shutDown_)
+ throw new Error('DataSender has been closed');
+ if (this.pendingCancel_)
+ throw new Error('Cancel in progress');
+ var send = new PendingSend(data);
+ this.sendsAwaitingAck_.push(send);
+ send.sendData(this.sink_).then(this.reportBytesSentAndError.bind(this));
+ return send.getPromise();
+ };
+
+ /**
+ * Requests the cancellation of any in-progress sends. Calls to
+ * [send()]{@link module:data_sender.DataSender#send} will fail until the
+ * cancel has completed.
+ * @param {number} error The error to report for cancelled sends.
+ * @return {!Promise} A promise that will resolve when the cancel completes.
+ * @throws Will throw if this has encountered a fatal error or another cancel
+ * is in progress.
+ */
+ DataSender.prototype.cancel = function(error) {
+ if (this.shutDown_)
+ throw new Error('DataSender has been closed');
+ if (this.pendingCancel_)
+ throw new Error('Cancel already in progress');
+ if (this.sendsAwaitingAck_.length == 0)
+ return Promise.resolve();
+
+ this.sink_.cancel(error);
+ this.cancelPromise_ = new Promise(function(resolve) {
+ this.pendingCancel_ = resolve;
+ }.bind(this));
+ return this.cancelPromise_;
+ };
+
+ /**
+ * Calls and clears the pending cancel callback if one is pending.
+ * @private
+ */
+ DataSender.prototype.callCancelCallback_ = function() {
+ if (this.pendingCancel_) {
+ this.cancelPromise_ = null;
+ this.pendingCancel_();
+ this.pendingCancel_ = null;
+ }
+ };
+
+ /**
+ * Invoked by the DataSink to report that data has been successfully sent.
+ * @private
+ */
+ DataSender.prototype.reportBytesSent = function() {
+ var result = this.sendsAwaitingAck_[0].reportBytesSent();
+ this.sendsAwaitingAck_.shift();
+
+ // A cancel is completed when all of the sends that were in progress have
+ // completed or failed. This is the case where all sends complete
+ // successfully.
+ if (this.sendsAwaitingAck_.length == 0)
+ this.callCancelCallback_();
+ };
+
+ /**
+ * Invoked by the DataSink to report an error in sending data.
+ * @param {number} numBytes The number of bytes sent.
+ * @param {number} error The error reported by the DataSink.
+ * @private
+ */
+ DataSender.prototype.reportBytesSentAndError = function(result) {
+ var numBytes = result.bytes_sent;
+ var error = result.error;
+ if (!error) {
+ this.reportBytesSent();
+ return;
+ }
+ var result =
+ this.sendsAwaitingAck_[0].reportBytesSentAndError(numBytes, error);
+ this.sendsAwaitingAck_.shift();
+ if (this.sendsAwaitingAck_.length)
+ return;
+ this.callCancelCallback_();
+ this.sink_.clearError();
+ };
+
+ return {DataSender: DataSender};
+});
diff --git a/chromium/extensions/renderer/resources/declarative_webrequest_custom_bindings.js b/chromium/extensions/renderer/resources/declarative_webrequest_custom_bindings.js
new file mode 100644
index 00000000000..e9da12b7077
--- /dev/null
+++ b/chromium/extensions/renderer/resources/declarative_webrequest_custom_bindings.js
@@ -0,0 +1,96 @@
+// 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.
+
+// Custom binding for the declarativeWebRequest API.
+
+var binding = require('binding').Binding.create('declarativeWebRequest');
+
+var utils = require('utils');
+var validate = require('schemaUtils').validate;
+
+binding.registerCustomHook(function(api) {
+ var declarativeWebRequest = api.compiledApi;
+
+ // Returns the schema definition of type |typeId| defined in |namespace|.
+ function getSchema(typeId) {
+ return utils.lookup(api.schema.types,
+ 'id',
+ 'declarativeWebRequest.' + typeId);
+ }
+
+ // Helper function for the constructor of concrete datatypes of the
+ // declarative webRequest API.
+ // Makes sure that |this| contains the union of parameters and
+ // {'instanceType': 'declarativeWebRequest.' + typeId} and validates the
+ // generated union dictionary against the schema for |typeId|.
+ function setupInstance(instance, parameters, typeId) {
+ for (var key in parameters) {
+ if ($Object.hasOwnProperty(parameters, key)) {
+ instance[key] = parameters[key];
+ }
+ }
+ instance.instanceType = 'declarativeWebRequest.' + typeId;
+ var schema = getSchema(typeId);
+ validate([instance], [schema]);
+ }
+
+ // Setup all data types for the declarative webRequest API.
+ declarativeWebRequest.RequestMatcher = function(parameters) {
+ setupInstance(this, parameters, 'RequestMatcher');
+ };
+ declarativeWebRequest.CancelRequest = function(parameters) {
+ setupInstance(this, parameters, 'CancelRequest');
+ };
+ declarativeWebRequest.RedirectRequest = function(parameters) {
+ setupInstance(this, parameters, 'RedirectRequest');
+ };
+ declarativeWebRequest.SetRequestHeader = function(parameters) {
+ setupInstance(this, parameters, 'SetRequestHeader');
+ };
+ declarativeWebRequest.RemoveRequestHeader = function(parameters) {
+ setupInstance(this, parameters, 'RemoveRequestHeader');
+ };
+ declarativeWebRequest.AddResponseHeader = function(parameters) {
+ setupInstance(this, parameters, 'AddResponseHeader');
+ };
+ declarativeWebRequest.RemoveResponseHeader = function(parameters) {
+ setupInstance(this, parameters, 'RemoveResponseHeader');
+ };
+ declarativeWebRequest.RedirectToTransparentImage =
+ function(parameters) {
+ setupInstance(this, parameters, 'RedirectToTransparentImage');
+ };
+ declarativeWebRequest.RedirectToEmptyDocument = function(parameters) {
+ setupInstance(this, parameters, 'RedirectToEmptyDocument');
+ };
+ declarativeWebRequest.RedirectByRegEx = function(parameters) {
+ setupInstance(this, parameters, 'RedirectByRegEx');
+ };
+ declarativeWebRequest.IgnoreRules = function(parameters) {
+ setupInstance(this, parameters, 'IgnoreRules');
+ };
+ declarativeWebRequest.AddRequestCookie = function(parameters) {
+ setupInstance(this, parameters, 'AddRequestCookie');
+ };
+ declarativeWebRequest.AddResponseCookie = function(parameters) {
+ setupInstance(this, parameters, 'AddResponseCookie');
+ };
+ declarativeWebRequest.EditRequestCookie = function(parameters) {
+ setupInstance(this, parameters, 'EditRequestCookie');
+ };
+ declarativeWebRequest.EditResponseCookie = function(parameters) {
+ setupInstance(this, parameters, 'EditResponseCookie');
+ };
+ declarativeWebRequest.RemoveRequestCookie = function(parameters) {
+ setupInstance(this, parameters, 'RemoveRequestCookie');
+ };
+ declarativeWebRequest.RemoveResponseCookie = function(parameters) {
+ setupInstance(this, parameters, 'RemoveResponseCookie');
+ };
+ declarativeWebRequest.SendMessageToExtension = function(parameters) {
+ setupInstance(this, parameters, 'SendMessageToExtension');
+ };
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/display_source_custom_bindings.js b/chromium/extensions/renderer/resources/display_source_custom_bindings.js
new file mode 100644
index 00000000000..38d7e3c791e
--- /dev/null
+++ b/chromium/extensions/renderer/resources/display_source_custom_bindings.js
@@ -0,0 +1,69 @@
+// Copyright 2015 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.
+
+// Custom binding for the Display Source API.
+
+var binding = require('binding').Binding.create('displaySource');
+var chrome = requireNative('chrome').GetChrome();
+var lastError = require('lastError');
+var natives = requireNative('display_source');
+var logging = requireNative('logging');
+
+var callbacksInfo = {};
+
+function callbackWrapper(callback, method, message) {
+ if (callback == undefined)
+ return;
+
+ try {
+ if (message !== null)
+ lastError.set(method, message, null, chrome);
+ callback();
+ } finally {
+ lastError.clear(chrome);
+ }
+}
+
+function callCompletionCallback(callbackId, error_message) {
+ try {
+ var callbackInfo = callbacksInfo[callbackId];
+ logging.DCHECK(callbackInfo != null);
+ callbackWrapper(callbackInfo.callback, callbackInfo.method, error_message);
+ } finally {
+ delete callbacksInfo[callbackId];
+ }
+}
+
+binding.registerCustomHook(function(bindingsAPI, extensionId) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+ apiFunctions.setHandleRequest(
+ 'startSession', function(sessionInfo, callback) {
+ try {
+ var callId = natives.StartSession(sessionInfo);
+ callbacksInfo[callId] = {
+ callback: callback,
+ method: 'displaySource.startSession'
+ };
+ } catch (e) {
+ callbackWrapper(callback, 'displaySource.startSession', e.message);
+ }
+ });
+ apiFunctions.setHandleRequest(
+ 'terminateSession', function(sink_id, callback) {
+ try {
+ var callId = natives.TerminateSession(sink_id);
+ callbacksInfo[callId] = {
+ callback: callback,
+ method: 'displaySource.terminateSession'
+ };
+ } catch (e) {
+ callbackWrapper(
+ callback, 'displaySource.terminateSession', e.message);
+ }
+ });
+});
+
+exports.$set('binding', binding.generate());
+// Called by C++.
+exports.$set('callCompletionCallback', callCompletionCallback);
diff --git a/chromium/extensions/renderer/resources/entry_id_manager.js b/chromium/extensions/renderer/resources/entry_id_manager.js
new file mode 100644
index 00000000000..8e23942f93a
--- /dev/null
+++ b/chromium/extensions/renderer/resources/entry_id_manager.js
@@ -0,0 +1,52 @@
+// Copyright 2014 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.
+
+var fileSystemNatives = requireNative('file_system_natives');
+
+var nameToIds = {};
+var idsToEntries = {};
+
+function computeName(entry) {
+ return entry.filesystem.name + ':' + entry.fullPath;
+}
+
+function computeId(entry) {
+ var fileSystemId = fileSystemNatives.CrackIsolatedFileSystemName(
+ entry.filesystem.name);
+ if (!fileSystemId)
+ return null;
+ // Strip the leading '/' from the path.
+ return fileSystemId + ':' + $String.slice(entry.fullPath, 1);
+}
+
+function registerEntry(id, entry) {
+ var name = computeName(entry);
+ nameToIds[name] = id;
+ idsToEntries[id] = entry;
+}
+
+function getEntryId(entry) {
+ var name = null;
+ try {
+ name = computeName(entry);
+ } catch(e) {
+ return null;
+ }
+ var id = nameToIds[name];
+ if (id != null)
+ return id;
+
+ // If an entry has not been registered, compute its id and register it.
+ id = computeId(entry);
+ registerEntry(id, entry);
+ return id;
+}
+
+function getEntryById(id) {
+ return idsToEntries[id];
+}
+
+exports.$set('registerEntry', registerEntry);
+exports.$set('getEntryId', getEntryId);
+exports.$set('getEntryById', getEntryById);
diff --git a/chromium/extensions/renderer/resources/event.js b/chromium/extensions/renderer/resources/event.js
new file mode 100644
index 00000000000..2f9aa3c7a55
--- /dev/null
+++ b/chromium/extensions/renderer/resources/event.js
@@ -0,0 +1,520 @@
+// Copyright 2014 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.
+
+// TODO(robwu): Fix indentation.
+
+ var exceptionHandler = require('uncaught_exception_handler');
+ var eventNatives = requireNative('event_natives');
+ var logging = requireNative('logging');
+ var schemaRegistry = requireNative('schema_registry');
+ var sendRequest = require('sendRequest').sendRequest;
+ var utils = require('utils');
+ var validate = require('schemaUtils').validate;
+
+ // Schemas for the rule-style functions on the events API that
+ // only need to be generated occasionally, so populate them lazily.
+ var ruleFunctionSchemas = {
+ __proto__: null,
+ // These values are set lazily:
+ // addRules: {},
+ // getRules: {},
+ // removeRules: {}
+ };
+
+ // This function ensures that |ruleFunctionSchemas| is populated.
+ function ensureRuleSchemasLoaded() {
+ if (ruleFunctionSchemas.addRules)
+ return;
+ var eventsSchema = schemaRegistry.GetSchema("events");
+ var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event');
+
+ ruleFunctionSchemas.addRules =
+ utils.lookup(eventType.functions, 'name', 'addRules');
+ ruleFunctionSchemas.getRules =
+ utils.lookup(eventType.functions, 'name', 'getRules');
+ ruleFunctionSchemas.removeRules =
+ utils.lookup(eventType.functions, 'name', 'removeRules');
+ }
+
+ // A map of event names to the event object that is registered to that name.
+ var attachedNamedEvents = {__proto__: null};
+
+ // A map of functions that massage event arguments before they are dispatched.
+ // Key is event name, value is function.
+ var eventArgumentMassagers = {__proto__: null};
+
+ // An attachment strategy for events that aren't attached to the browser.
+ // This applies to events with the "unmanaged" option and events without
+ // names.
+ function NullAttachmentStrategy(event) {
+ this.event_ = event;
+ }
+ $Object.setPrototypeOf(NullAttachmentStrategy.prototype, null);
+
+ NullAttachmentStrategy.prototype.onAddedListener =
+ function(listener) {
+ };
+ NullAttachmentStrategy.prototype.onRemovedListener =
+ function(listener) {
+ };
+ NullAttachmentStrategy.prototype.detach = function(manual) {
+ };
+ NullAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
+ // |ids| is for filtered events only.
+ return this.event_.listeners;
+ };
+
+ // Handles adding/removing/dispatching listeners for unfiltered events.
+ function UnfilteredAttachmentStrategy(event) {
+ this.event_ = event;
+ }
+ $Object.setPrototypeOf(UnfilteredAttachmentStrategy.prototype, null);
+
+ UnfilteredAttachmentStrategy.prototype.onAddedListener =
+ function(listener) {
+ // Only attach / detach on the first / last listener removed.
+ if (this.event_.listeners.length == 0)
+ eventNatives.AttachEvent(this.event_.eventName);
+ };
+
+ UnfilteredAttachmentStrategy.prototype.onRemovedListener =
+ function(listener) {
+ if (this.event_.listeners.length == 0)
+ this.detach(true);
+ };
+
+ UnfilteredAttachmentStrategy.prototype.detach = function(manual) {
+ eventNatives.DetachEvent(this.event_.eventName, manual);
+ };
+
+ UnfilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
+ // |ids| is for filtered events only.
+ return this.event_.listeners;
+ };
+
+ function FilteredAttachmentStrategy(event) {
+ this.event_ = event;
+ this.listenerMap_ = {__proto__: null};
+ }
+ $Object.setPrototypeOf(FilteredAttachmentStrategy.prototype, null);
+
+ utils.defineProperty(FilteredAttachmentStrategy, 'idToEventMap',
+ {__proto__: null});
+
+ FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) {
+ var id = eventNatives.AttachFilteredEvent(this.event_.eventName,
+ listener.filters || {});
+ if (id == -1)
+ throw new Error("Can't add listener");
+ listener.id = id;
+ this.listenerMap_[id] = listener;
+ FilteredAttachmentStrategy.idToEventMap[id] = this.event_;
+ };
+
+ FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) {
+ this.detachListener(listener, true);
+ };
+
+ FilteredAttachmentStrategy.prototype.detachListener =
+ function(listener, manual) {
+ if (listener.id == undefined)
+ throw new Error("listener.id undefined - '" + listener + "'");
+ var id = listener.id;
+ delete this.listenerMap_[id];
+ delete FilteredAttachmentStrategy.idToEventMap[id];
+ eventNatives.DetachFilteredEvent(id, manual);
+ };
+
+ FilteredAttachmentStrategy.prototype.detach = function(manual) {
+ for (var i in this.listenerMap_)
+ this.detachListener(this.listenerMap_[i], manual);
+ };
+
+ FilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
+ var result = [];
+ for (var i = 0; i < ids.length; i++)
+ $Array.push(result, this.listenerMap_[ids[i]]);
+ return result;
+ };
+
+ function parseEventOptions(opt_eventOptions) {
+ return $Object.assign({
+ __proto__: null,
+ }, {
+ // Event supports adding listeners with filters ("filtered events"), for
+ // example as used in the webNavigation API.
+ //
+ // event.addListener(listener, [filter1, filter2]);
+ supportsFilters: false,
+
+ // Events supports vanilla events. Most APIs use these.
+ //
+ // event.addListener(listener);
+ supportsListeners: true,
+
+ // Event supports adding rules ("declarative events") rather than
+ // listeners, for example as used in the declarativeWebRequest API.
+ //
+ // event.addRules([rule1, rule2]);
+ supportsRules: false,
+
+ // Event is unmanaged in that the browser has no knowledge of its
+ // existence; it's never invoked, doesn't keep the renderer alive, and
+ // the bindings system has no knowledge of it.
+ //
+ // Both events created by user code (new chrome.Event()) and messaging
+ // events are unmanaged, though in the latter case the browser *does*
+ // interact indirectly with them via IPCs written by hand.
+ unmanaged: false,
+ }, opt_eventOptions);
+ }
+
+ // Event object. If opt_eventName is provided, this object represents
+ // the unique instance of that named event, and dispatching an event
+ // with that name will route through this object's listeners. Note that
+ // opt_eventName is required for events that support rules.
+ //
+ // Example:
+ // var Event = require('event_bindings').Event;
+ // chrome.tabs.onChanged = new Event("tab-changed");
+ // chrome.tabs.onChanged.addListener(function(data) { alert(data); });
+ // Event.dispatch("tab-changed", "hi");
+ // will result in an alert dialog that says 'hi'.
+ //
+ // If opt_eventOptions exists, it is a dictionary that contains the boolean
+ // entries "supportsListeners" and "supportsRules".
+ // If opt_webViewInstanceId exists, it is an integer uniquely identifying a
+ // <webview> tag within the embedder. If it does not exist, then this is an
+ // extension event rather than a <webview> event.
+ function EventImpl(opt_eventName, opt_argSchemas, opt_eventOptions,
+ opt_webViewInstanceId) {
+ this.eventName = opt_eventName;
+ this.argSchemas = opt_argSchemas;
+ this.listeners = [];
+ this.eventOptions = parseEventOptions(opt_eventOptions);
+ this.webViewInstanceId = opt_webViewInstanceId || 0;
+
+ if (!this.eventName) {
+ if (this.eventOptions.supportsRules)
+ throw new Error("Events that support rules require an event name.");
+ // Events without names cannot be managed by the browser by definition
+ // (the browser has no way of identifying them).
+ this.eventOptions.unmanaged = true;
+ }
+
+ // Track whether the event has been destroyed to help track down the cause
+ // of http://crbug.com/258526.
+ // This variable will eventually hold the stack trace of the destroy call.
+ // TODO(kalman): Delete this and replace with more sound logic that catches
+ // when events are used without being *attached*.
+ this.destroyed = null;
+
+ if (this.eventOptions.unmanaged)
+ this.attachmentStrategy = new NullAttachmentStrategy(this);
+ else if (this.eventOptions.supportsFilters)
+ this.attachmentStrategy = new FilteredAttachmentStrategy(this);
+ else
+ this.attachmentStrategy = new UnfilteredAttachmentStrategy(this);
+ }
+ $Object.setPrototypeOf(EventImpl.prototype, null);
+
+ // callback is a function(args, dispatch). args are the args we receive from
+ // dispatchEvent(), and dispatch is a function(args) that dispatches args to
+ // its listeners.
+ function registerArgumentMassager(name, callback) {
+ if (eventArgumentMassagers[name])
+ throw new Error("Massager already registered for event: " + name);
+ eventArgumentMassagers[name] = callback;
+ }
+
+ // Dispatches a named event with the given argument array. The args array is
+ // the list of arguments that will be sent to the event callback.
+ function dispatchEvent(name, args, filteringInfo) {
+ var listenerIDs = [];
+
+ if (filteringInfo)
+ listenerIDs = eventNatives.MatchAgainstEventFilter(name, filteringInfo);
+
+ var event = attachedNamedEvents[name];
+ if (!event)
+ return;
+
+ var dispatchArgs = function(args) {
+ var result = event.dispatch_(args, listenerIDs);
+ if (result)
+ logging.DCHECK(!result.validationErrors, result.validationErrors);
+ return result;
+ };
+
+ if (eventArgumentMassagers[name])
+ eventArgumentMassagers[name](args, dispatchArgs);
+ else
+ dispatchArgs(args);
+ }
+
+ // Registers a callback to be called when this event is dispatched.
+ EventImpl.prototype.addListener = function(cb, filters) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error("This event does not support listeners.");
+ if (this.eventOptions.maxListeners &&
+ this.getListenerCount_() >= this.eventOptions.maxListeners) {
+ throw new Error("Too many listeners for " + this.eventName);
+ }
+ if (filters) {
+ if (!this.eventOptions.supportsFilters)
+ throw new Error("This event does not support filters.");
+ if (filters.url && !(filters.url instanceof Array))
+ throw new Error("filters.url should be an array.");
+ if (filters.serviceType &&
+ !(typeof filters.serviceType === 'string')) {
+ throw new Error("filters.serviceType should be a string.")
+ }
+ }
+ var listener = {callback: cb, filters: filters};
+ this.attach_(listener);
+ $Array.push(this.listeners, listener);
+ };
+
+ EventImpl.prototype.attach_ = function(listener) {
+ this.attachmentStrategy.onAddedListener(listener);
+
+ if (this.listeners.length == 0) {
+ if (this.eventName) {
+ if (attachedNamedEvents[this.eventName]) {
+ throw new Error("Event '" + this.eventName +
+ "' is already attached.");
+ }
+ attachedNamedEvents[this.eventName] = this;
+ }
+ }
+ };
+
+ // Unregisters a callback.
+ EventImpl.prototype.removeListener = function(cb) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error("This event does not support listeners.");
+
+ var idx = this.findListener_(cb);
+ if (idx == -1)
+ return;
+
+ var removedListener = $Array.splice(this.listeners, idx, 1)[0];
+ this.attachmentStrategy.onRemovedListener(removedListener);
+
+ if (this.listeners.length == 0) {
+ if (this.eventName) {
+ if (!attachedNamedEvents[this.eventName]) {
+ throw new Error(
+ "Event '" + this.eventName + "' is not attached.");
+ }
+ delete attachedNamedEvents[this.eventName];
+ }
+ }
+ };
+
+ // Test if the given callback is registered for this event.
+ EventImpl.prototype.hasListener = function(cb) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error("This event does not support listeners.");
+ return this.findListener_(cb) > -1;
+ };
+
+ // Test if any callbacks are registered for this event.
+ EventImpl.prototype.hasListeners = function() {
+ return this.getListenerCount_() > 0;
+ };
+
+ // Returns the number of listeners on this event.
+ EventImpl.prototype.getListenerCount_ = function() {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error("This event does not support listeners.");
+ return this.listeners.length;
+ };
+
+ // Returns the index of the given callback if registered, or -1 if not
+ // found.
+ EventImpl.prototype.findListener_ = function(cb) {
+ for (var i = 0; i < this.listeners.length; i++) {
+ if (this.listeners[i].callback == cb) {
+ return i;
+ }
+ }
+
+ return -1;
+ };
+
+ EventImpl.prototype.dispatch_ = function(args, listenerIDs) {
+ if (this.destroyed) {
+ throw new Error(this.eventName + ' was already destroyed at: ' +
+ this.destroyed);
+ }
+ if (!this.eventOptions.supportsListeners)
+ throw new Error("This event does not support listeners.");
+
+ if (this.argSchemas && logging.DCHECK_IS_ON()) {
+ try {
+ validate(args, this.argSchemas);
+ } catch (e) {
+ e.message += ' in ' + this.eventName;
+ throw e;
+ }
+ }
+
+ // Make a copy of the listeners in case the listener list is modified
+ // while dispatching the event.
+ var listeners = $Array.slice(
+ this.attachmentStrategy.getListenersByIDs(listenerIDs));
+
+ var results = [];
+ for (var i = 0; i < listeners.length; i++) {
+ try {
+ var result = this.wrapper.dispatchToListener(listeners[i].callback,
+ args);
+ if (result !== undefined)
+ $Array.push(results, result);
+ } catch (e) {
+ exceptionHandler.handle('Error in event handler for ' +
+ (this.eventName ? this.eventName : '(unknown)'),
+ e);
+ }
+ }
+ if (results.length)
+ return {results: results};
+ }
+
+ // Can be overridden to support custom dispatching.
+ EventImpl.prototype.dispatchToListener = function(callback, args) {
+ return $Function.apply(callback, null, args);
+ }
+
+ // Dispatches this event object to all listeners, passing all supplied
+ // arguments to this function each listener.
+ EventImpl.prototype.dispatch = function(varargs) {
+ return this.dispatch_($Array.slice(arguments), undefined);
+ };
+
+ // Detaches this event object from its name.
+ EventImpl.prototype.detach_ = function() {
+ this.attachmentStrategy.detach(false);
+ };
+
+ EventImpl.prototype.destroy_ = function() {
+ this.listeners.length = 0;
+ this.detach_();
+ this.destroyed = exceptionHandler.getStackTrace();
+ };
+
+ EventImpl.prototype.addRules = function(rules, opt_cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error("This event does not support rules.");
+
+ // Takes a list of JSON datatype identifiers and returns a schema fragment
+ // that verifies that a JSON object corresponds to an array of only these
+ // data types.
+ function buildArrayOfChoicesSchema(typesList) {
+ return {
+ __proto__: null,
+ 'type': 'array',
+ 'items': {
+ __proto__: null,
+ 'choices': $Array.map(typesList, function(el) {
+ return {
+ __proto__: null,
+ '$ref': el,
+ };
+ }),
+ }
+ };
+ }
+
+ // Validate conditions and actions against specific schemas of this
+ // event object type.
+ // |rules| is an array of JSON objects that follow the Rule type of the
+ // declarative extension APIs. |conditions| is an array of JSON type
+ // identifiers that are allowed to occur in the conditions attribute of each
+ // rule. Likewise, |actions| is an array of JSON type identifiers that are
+ // allowed to occur in the actions attribute of each rule.
+ function validateRules(rules, conditions, actions) {
+ var conditionsSchema = buildArrayOfChoicesSchema(conditions);
+ var actionsSchema = buildArrayOfChoicesSchema(actions);
+ $Array.forEach(rules, function(rule) {
+ validate([rule.conditions], [conditionsSchema]);
+ validate([rule.actions], [actionsSchema]);
+ });
+ };
+
+ if (!this.eventOptions.conditions || !this.eventOptions.actions) {
+ throw new Error('Event ' + this.eventName + ' misses ' +
+ 'conditions or actions in the API specification.');
+ }
+
+ validateRules(rules,
+ this.eventOptions.conditions,
+ this.eventOptions.actions);
+
+ ensureRuleSchemasLoaded();
+ // We remove the first parameter from the validation to give the user more
+ // meaningful error messages.
+ validate([this.webViewInstanceId, rules, opt_cb],
+ $Array.slice(ruleFunctionSchemas.addRules.parameters, 1));
+ sendRequest(
+ "events.addRules",
+ [this.eventName, this.webViewInstanceId, rules, opt_cb],
+ ruleFunctionSchemas.addRules.parameters);
+ }
+
+ EventImpl.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error("This event does not support rules.");
+ ensureRuleSchemasLoaded();
+ // We remove the first parameter from the validation to give the user more
+ // meaningful error messages.
+ validate([this.webViewInstanceId, ruleIdentifiers, opt_cb],
+ $Array.slice(ruleFunctionSchemas.removeRules.parameters, 1));
+ sendRequest("events.removeRules",
+ [this.eventName,
+ this.webViewInstanceId,
+ ruleIdentifiers,
+ opt_cb],
+ ruleFunctionSchemas.removeRules.parameters);
+ }
+
+ EventImpl.prototype.getRules = function(ruleIdentifiers, cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error("This event does not support rules.");
+ ensureRuleSchemasLoaded();
+ // We remove the first parameter from the validation to give the user more
+ // meaningful error messages.
+ validate([this.webViewInstanceId, ruleIdentifiers, cb],
+ $Array.slice(ruleFunctionSchemas.getRules.parameters, 1));
+
+ sendRequest(
+ "events.getRules",
+ [this.eventName, this.webViewInstanceId, ruleIdentifiers, cb],
+ ruleFunctionSchemas.getRules.parameters);
+ }
+
+ function Event() {
+ privates(Event).constructPrivate(this, arguments);
+ }
+ utils.expose(Event, EventImpl, {
+ functions: [
+ 'addListener',
+ 'removeListener',
+ 'hasListener',
+ 'hasListeners',
+ 'dispatchToListener',
+ 'dispatch',
+ 'addRules',
+ 'removeRules',
+ 'getRules',
+ ],
+ });
+
+ // NOTE: Event is (lazily) exposed as chrome.Event from dispatcher.cc.
+ exports.$set('Event', Event);
+
+ exports.$set('dispatchEvent', dispatchEvent);
+ exports.$set('parseEventOptions', parseEventOptions);
+ exports.$set('registerArgumentMassager', registerArgumentMassager);
diff --git a/chromium/extensions/renderer/resources/extension.css b/chromium/extensions/renderer/resources/extension.css
new file mode 100644
index 00000000000..808a08ce151
--- /dev/null
+++ b/chromium/extensions/renderer/resources/extension.css
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2014 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.
+ *
+ * This stylesheet is used to apply Chrome styles to extension pages that opt in
+ * to using them.
+ *
+ * These styles have been copied from ui/webui/resources/css/chrome_shared.css
+ * and ui/webui/resources/css/widgets.css *with CSS class logic removed*, so
+ * that it's as close to a user-agent stylesheet as possible.
+ *
+ * For example, extensions shouldn't be able to set a .link-button class and
+ * have it do anything.
+ *
+ * Other than that, keep this file and chrome_shared.css/widgets.cc in sync as
+ * much as possible.
+ */
+
+body {
+ color: #333;
+ cursor: default;
+ /* Note that the correct font-family and font-size are set in
+ * extension_fonts.css. */
+ /* This top margin of 14px matches the top padding on the h1 element on
+ * overlays (see the ".overlay .page h1" selector in overlay.css), which
+ * every dialogue has.
+ *
+ * Similarly, the bottom 14px margin matches the bottom padding of the area
+ * which hosts the buttons (see the ".overlay .page * .action-area" selector
+ * in overlay.css).
+ *
+ * Both have a padding left/right of 17px.
+ *
+ * Note that we're putting this here in the Extension content, rather than
+ * the WebUI element which contains the content, so that scrollbars in the
+ * Extension content don't get a 6px margin, which looks quite odd.
+ */
+ margin: 14px 17px;
+}
+
+p {
+ line-height: 1.8em;
+}
+
+h1,
+h2,
+h3 {
+ -webkit-user-select: none;
+ font-weight: normal;
+ /* Makes the vertical size of the text the same for all fonts. */
+ line-height: 1;
+}
+
+h1 {
+ font-size: 1.5em;
+}
+
+h2 {
+ font-size: 1.3em;
+ margin-bottom: 0.4em;
+}
+
+h3 {
+ color: black;
+ font-size: 1.2em;
+ margin-bottom: 0.8em;
+}
+
+a {
+ color: rgb(17, 85, 204);
+ text-decoration: underline;
+}
+
+a:active {
+ color: rgb(5, 37, 119);
+}
+
+/* Default state **************************************************************/
+
+:-webkit-any(button,
+ input[type='button'],
+ input[type='submit']),
+select,
+input[type='checkbox'],
+input[type='radio'] {
+ -webkit-appearance: none;
+ -webkit-user-select: none;
+ background-image: linear-gradient(#ededed, #ededed 38%, #dedede);
+ border: 1px solid rgba(0, 0, 0, 0.25);
+ border-radius: 2px;
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
+ inset 0 1px 2px rgba(255, 255, 255, 0.75);
+ color: #444;
+ font: inherit;
+ margin: 0 1px 0 0;
+ outline: none;
+ text-shadow: 0 1px 0 rgb(240, 240, 240);
+}
+
+:-webkit-any(button,
+ input[type='button'],
+ input[type='submit']),
+select {
+ min-height: 2em;
+ min-width: 4em;
+<if expr="is_win">
+ /* The following platform-specific rule is necessary to get adjacent
+ * buttons, text inputs, and so forth to align on their borders while also
+ * aligning on the text's baselines. */
+ padding-bottom: 1px;
+</if>
+}
+
+:-webkit-any(button,
+ input[type='button'],
+ input[type='submit']) {
+ -webkit-padding-end: 10px;
+ -webkit-padding-start: 10px;
+}
+
+select {
+ -webkit-appearance: none;
+ -webkit-padding-end: 20px;
+ -webkit-padding-start: 6px;
+ /* OVERRIDE */
+ background-image: url(../../../ui/webui/resources/images/select.png),
+ linear-gradient(#ededed, #ededed 38%, #dedede);
+ background-position: right center;
+ background-repeat: no-repeat;
+}
+
+html[dir='rtl'] select {
+ background-position: center left;
+}
+
+input[type='checkbox'] {
+ height: 13px;
+ position: relative;
+ vertical-align: middle;
+ width: 13px;
+}
+
+input[type='radio'] {
+ /* OVERRIDE */
+ border-radius: 100%;
+ height: 15px;
+ position: relative;
+ vertical-align: middle;
+ width: 15px;
+}
+
+/* TODO(estade): add more types here? */
+input[type='number'],
+input[type='password'],
+input[type='search'],
+input[type='text'],
+input[type='url'],
+input:not([type]),
+textarea {
+ border: 1px solid #bfbfbf;
+ border-radius: 2px;
+ box-sizing: border-box;
+ color: #444;
+ font: inherit;
+ margin: 0;
+ /* Use min-height to accommodate addditional padding for touch as needed. */
+ min-height: 2em;
+ padding: 3px;
+ outline: none;
+<if expr="is_win or is_macosx or is_ios">
+ /* For better alignment between adjacent buttons and inputs. */
+ padding-bottom: 4px;
+</if>
+}
+
+input[type='search'] {
+ -webkit-appearance: textfield;
+ /* NOTE: Keep a relatively high min-width for this so we don't obscure the end
+ * of the default text in relatively spacious languages (i.e. German). */
+ min-width: 160px;
+}
+
+/* Remove when https://bugs.webkit.org/show_bug.cgi?id=51499 is fixed.
+ * TODO(dbeam): are there more types that would benefit from this? */
+input[type='search']::-webkit-textfield-decoration-container {
+ direction: inherit;
+}
+
+/* Checked ********************************************************************/
+
+input[type='checkbox']:checked::before {
+ -webkit-user-select: none;
+ background-image: url(../../../ui/webui/resources/images/check.png);
+ background-size: 100% 100%;
+ content: '';
+ display: block;
+ height: 100%;
+ width: 100%;
+}
+
+input[type='radio']:checked::before {
+ background-color: #666;
+ border-radius: 100%;
+ bottom: 3px;
+ content: '';
+ display: block;
+ left: 3px;
+ position: absolute;
+ right: 3px;
+ top: 3px;
+}
+
+/* Hover **********************************************************************/
+
+:enabled:hover:-webkit-any(
+ select,
+ input[type='checkbox'],
+ input[type='radio'],
+ :-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit'])) {
+ background-image: linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
+ border-color: rgba(0, 0, 0, 0.3);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12),
+ inset 0 1px 2px rgba(255, 255, 255, 0.95);
+ color: black;
+}
+
+:enabled:hover:-webkit-any(select) {
+ /* OVERRIDE */
+ background-image: url(../../../ui/webui/resources/images/select.png),
+ linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
+}
+
+/* Active *********************************************************************/
+
+:enabled:active:-webkit-any(
+ select,
+ input[type='checkbox'],
+ input[type='radio'],
+ :-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit'])) {
+ background-image: linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
+ box-shadow: none;
+ text-shadow: none;
+}
+
+:enabled:active:-webkit-any(select) {
+ /* OVERRIDE */
+ background-image: url(../../../ui/webui/resources/images/select.png),
+ linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
+}
+
+/* Disabled *******************************************************************/
+
+:disabled:-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit']),
+select:disabled {
+ background-image: linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
+ border-color: rgba(80, 80, 80, 0.2);
+ box-shadow: 0 1px 0 rgba(80, 80, 80, 0.08),
+ inset 0 1px 2px rgba(255, 255, 255, 0.75);
+ color: #aaa;
+}
+
+select:disabled {
+ /* OVERRIDE */
+ background-image: url(../../../ui/webui/resources/images/disabled_select.png),
+ linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
+}
+
+input:disabled:-webkit-any([type='checkbox'],
+ [type='radio']) {
+ opacity: .75;
+}
+
+input:disabled:-webkit-any([type='password'],
+ [type='search'],
+ [type='text'],
+ [type='url'],
+ :not([type])) {
+ color: #999;
+}
+
+/* Focus **********************************************************************/
+
+:enabled:focus:-webkit-any(
+ select,
+ input[type='checkbox'],
+ input[type='number'],
+ input[type='password'],
+ input[type='radio'],
+ input[type='search'],
+ input[type='text'],
+ input[type='url'],
+ input:not([type]),
+ :-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit'])) {
+ /* OVERRIDE */
+ -webkit-transition: border-color 200ms;
+ /* We use border color because it follows the border radius (unlike outline).
+ * This is particularly noticeable on mac. */
+ border-color: rgb(77, 144, 254);
+ outline: none;
+}
+
+/* Checkbox/radio helpers ******************************************************
+ *
+ * .checkbox and .radio classes wrap labels. Checkboxes and radios should use
+ * these classes with the markup structure:
+ *
+ * <div class="checkbox">
+ * <label>
+ * <input type="checkbox"></input>
+ * <span>
+ * </label>
+ * </div>
+ */
+
+:-webkit-any(.checkbox, .radio) label {
+ /* Don't expand horizontally: <http://crbug.com/112091>. */
+ align-items: center;
+ display: inline-flex;
+ padding-bottom: 7px;
+ padding-top: 7px;
+}
+
+:-webkit-any(.checkbox, .radio) label input {
+ flex-shrink: 0;
+}
+
+:-webkit-any(.checkbox, .radio) label input ~ span {
+ -webkit-margin-start: 0.6em;
+ /* Make sure long spans wrap at the same horizontal position they start. */
+ display: block;
+}
+
+:-webkit-any(.checkbox, .radio) label:hover {
+ color: black;
+}
+
+label > input:disabled:-webkit-any([type='checkbox'], [type='radio']) ~ span {
+ color: #999;
+}
diff --git a/chromium/extensions/renderer/resources/extension_custom_bindings.js b/chromium/extensions/renderer/resources/extension_custom_bindings.js
new file mode 100644
index 00000000000..68450d56b3a
--- /dev/null
+++ b/chromium/extensions/renderer/resources/extension_custom_bindings.js
@@ -0,0 +1,111 @@
+// Copyright 2014 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.
+
+// Custom binding for the extension API.
+
+var binding = require('binding').Binding.create('extension');
+
+var messaging = require('messaging');
+var runtimeNatives = requireNative('runtime');
+var GetExtensionViews = runtimeNatives.GetExtensionViews;
+var chrome = requireNative('chrome').GetChrome();
+
+var inIncognitoContext = requireNative('process').InIncognitoContext();
+var sendRequestIsDisabled = requireNative('process').IsSendRequestDisabled();
+var contextType = requireNative('process').GetContextType();
+var manifestVersion = requireNative('process').GetManifestVersion();
+
+// This should match chrome.windows.WINDOW_ID_NONE.
+//
+// We can't use chrome.windows.WINDOW_ID_NONE directly because the
+// chrome.windows API won't exist unless this extension has permission for it;
+// which may not be the case.
+var WINDOW_ID_NONE = -1;
+
+binding.registerCustomHook(function(bindingsAPI, extensionId) {
+ var extension = bindingsAPI.compiledApi;
+ if (manifestVersion < 2) {
+ chrome.self = extension;
+ extension.inIncognitoTab = inIncognitoContext;
+ }
+ extension.inIncognitoContext = inIncognitoContext;
+
+ var apiFunctions = bindingsAPI.apiFunctions;
+
+ apiFunctions.setHandleRequest('getViews', function(properties) {
+ var windowId = WINDOW_ID_NONE;
+ var type = 'ALL';
+ if (properties) {
+ if (properties.type != null) {
+ type = properties.type;
+ }
+ if (properties.windowId != null) {
+ windowId = properties.windowId;
+ }
+ }
+ return GetExtensionViews(windowId, type);
+ });
+
+ apiFunctions.setHandleRequest('getBackgroundPage', function() {
+ return GetExtensionViews(-1, 'BACKGROUND')[0] || null;
+ });
+
+ apiFunctions.setHandleRequest('getExtensionTabs', function(windowId) {
+ if (windowId == null)
+ windowId = WINDOW_ID_NONE;
+ return GetExtensionViews(windowId, 'TAB');
+ });
+
+ apiFunctions.setHandleRequest('getURL', function(path) {
+ path = String(path);
+ if (!path.length || path[0] != '/')
+ path = '/' + path;
+ return 'chrome-extension://' + extensionId + path;
+ });
+
+ // Alias several messaging deprecated APIs to their runtime counterparts.
+ var mayNeedAlias = [
+ // Types
+ 'Port',
+ // Functions
+ 'connect', 'sendMessage', 'connectNative', 'sendNativeMessage',
+ // Events
+ 'onConnect', 'onConnectExternal', 'onMessage', 'onMessageExternal'
+ ];
+ $Array.forEach(mayNeedAlias, function(alias) {
+ // Checking existence isn't enough since some functions are disabled via
+ // getters that throw exceptions. Assume that any getter is such a function.
+ if (chrome.runtime &&
+ $Object.hasOwnProperty(chrome.runtime, alias) &&
+ chrome.runtime.__lookupGetter__(alias) === undefined) {
+ extension[alias] = chrome.runtime[alias];
+ }
+ });
+
+ apiFunctions.setUpdateArgumentsPreValidate('sendRequest',
+ $Function.bind(messaging.sendMessageUpdateArguments,
+ null, 'sendRequest', false /* hasOptionsArgument */));
+
+ apiFunctions.setHandleRequest('sendRequest',
+ function(targetId, request, responseCallback) {
+ if (sendRequestIsDisabled)
+ throw new Error(sendRequestIsDisabled);
+ var port = chrome.runtime.connect(targetId || extensionId,
+ {name: messaging.kRequestChannel});
+ messaging.sendMessageImpl(port, request, responseCallback);
+ });
+
+ if (sendRequestIsDisabled) {
+ extension.onRequest.addListener = function() {
+ throw new Error(sendRequestIsDisabled);
+ };
+ if (contextType == 'BLESSED_EXTENSION') {
+ extension.onRequestExternal.addListener = function() {
+ throw new Error(sendRequestIsDisabled);
+ };
+ }
+ }
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/extension_fonts.css b/chromium/extensions/renderer/resources/extension_fonts.css
new file mode 100644
index 00000000000..464f8ebfc99
--- /dev/null
+++ b/chromium/extensions/renderer/resources/extension_fonts.css
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2014 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.
+ *
+ * This stylesheet is used to apply Chrome system fonts to all extension pages.
+ */
+
+body {
+ font-family: $FONTFAMILY;
+ font-size: $FONTSIZE;
+}
diff --git a/chromium/extensions/renderer/resources/extensions_renderer_resources.grd b/chromium/extensions/renderer/resources/extensions_renderer_resources.grd
new file mode 100644
index 00000000000..808b931d542
--- /dev/null
+++ b/chromium/extensions/renderer/resources/extensions_renderer_resources.grd
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/extensions_renderer_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="extensions_renderer_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <!-- Extension libraries. -->
+ <include name="IDR_APP_VIEW_JS" file="guest_view/app_view/app_view.js" type="BINDATA" />
+ <include name="IDR_ASYNC_WAITER_JS" file="async_waiter.js" type="BINDATA" />
+ <include name="IDR_BROWSER_TEST_ENVIRONMENT_SPECIFIC_BINDINGS_JS" file="browser_test_environment_specific_bindings.js" type="BINDATA" />
+ <include name="IDR_DATA_RECEIVER_JS" file="data_receiver.js" type="BINDATA" />
+ <include name="IDR_DATA_SENDER_JS" file="data_sender.js" type="BINDATA" />
+ <include name="IDR_DATA_STREAM_MOJOM_JS" file="${mojom_root}\device\serial\data_stream.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_DATA_STREAM_SERIALIZATION_MOJOM_JS" file="${mojom_root}\device\serial\data_stream_serialization.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_ENTRY_ID_MANAGER" file="entry_id_manager.js" type="BINDATA" />
+ <include name="IDR_EVENT_BINDINGS_JS" file="event.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_OPTIONS_JS" file="guest_view/extension_options/extension_options.js" type="BINDATA"/>
+ <include name="IDR_EXTENSION_OPTIONS_ATTRIBUTES_JS" file="guest_view/extension_options/extension_options_attributes.js" type="BINDATA"/>
+ <include name="IDR_EXTENSION_OPTIONS_CONSTANTS_JS" file="guest_view/extension_options/extension_options_constants.js" type="BINDATA"/>
+ <include name="IDR_EXTENSION_OPTIONS_EVENTS_JS" file="guest_view/extension_options/extension_options_events.js" type="BINDATA"/>
+ <include name="IDR_EXTENSION_VIEW_JS" file="guest_view/extension_view/extension_view.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_VIEW_API_METHODS_JS" file="guest_view/extension_view/extension_view_api_methods.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_VIEW_ATTRIBUTES_JS" file="guest_view/extension_view/extension_view_attributes.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_VIEW_CONSTANTS_JS" file="guest_view/extension_view/extension_view_constants.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_VIEW_EVENTS_JS" file="guest_view/extension_view/extension_view_events.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_VIEW_INTERNAL_CUSTOM_BINDINGS_JS" file="guest_view/extension_view/extension_view_internal.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_ATTRIBUTES_JS" file="guest_view/guest_view_attributes.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_CONTAINER_JS" file="guest_view/guest_view_container.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_DENY_JS" file="guest_view/guest_view_deny.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_EVENTS_JS" file="guest_view/guest_view_events.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_IFRAME_CONTAINER_JS" file="guest_view/guest_view_iframe_container.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_IFRAME_JS" file="guest_view/guest_view_iframe.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_JS" file="guest_view/guest_view.js" type="BINDATA" />
+ <include name="IDR_IMAGE_UTIL_JS" file="image_util.js" type="BINDATA" />
+ <include name="IDR_JSON_SCHEMA_JS" file="json_schema.js" type="BINDATA" />
+ <include name="IDR_KEEP_ALIVE_JS" file="keep_alive.js" type="BINDATA" />
+ <include name="IDR_KEEP_ALIVE_MOJOM_JS" file="${mojom_root}\extensions\common\mojo\keep_alive.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_LAST_ERROR_JS" file="last_error.js" type="BINDATA" />
+ <include name="IDR_MESSAGING_JS" file="messaging.js" type="BINDATA" />
+ <include name="IDR_MESSAGING_UTILS_JS" file="messaging_utils.js" type="BINDATA" />
+ <include name="IDR_MIME_HANDLER_PRIVATE_CUSTOM_BINDINGS_JS" file="mime_handler_private_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_MIME_HANDLER_MOJOM_JS" file="${mojom_root}\extensions\common\api\mime_handler.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_SCHEMA_UTILS_JS" file="schema_utils.js" type="BINDATA" />
+ <include name="IDR_SEND_REQUEST_JS" file="send_request.js" type="BINDATA" />
+ <include name="IDR_SERIAL_CUSTOM_BINDINGS_JS" file="serial_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_SERIAL_MOJOM_JS" file="${mojom_root}\device\serial\serial.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_SERIAL_SERIALIZATION_MOJOM_JS" file="${mojom_root}\device\serial\serial_serialization.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_SERIAL_SERVICE_JS" file="serial_service.js" type="BINDATA" />
+ <include name="IDR_SET_ICON_JS" file="set_icon.js" type="BINDATA" />
+ <include name="IDR_STASH_CLIENT_JS" file="stash_client.js" type="BINDATA" />
+ <include name="IDR_STASH_MOJOM_JS" file="${mojom_root}\extensions\common\mojo\stash.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_TEST_CUSTOM_BINDINGS_JS" file="test_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_UNCAUGHT_EXCEPTION_HANDLER_JS" file="uncaught_exception_handler.js" type="BINDATA" />
+ <include name="IDR_UTILS_JS" file="utils.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_ACTION_REQUESTS_JS" file="guest_view/web_view/web_view_action_requests.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_API_METHODS_JS" file="guest_view/web_view/web_view_api_methods.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_ATTRIBUTES_JS" file="guest_view/web_view/web_view_attributes.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_CONSTANTS_JS" file="guest_view/web_view/web_view_constants.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_EVENTS_JS" file="guest_view/web_view/web_view_events.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_EXPERIMENTAL_JS" file="guest_view/web_view/web_view_experimental.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_INTERNAL_CUSTOM_BINDINGS_JS" file="guest_view/web_view/web_view_internal.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_JS" file="guest_view/web_view/web_view.js" type="BINDATA" />
+
+ <!-- Custom bindings for APIs. -->
+ <include name="IDR_APP_RUNTIME_CUSTOM_BINDINGS_JS" file="app_runtime_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_APP_WINDOW_CUSTOM_BINDINGS_JS" file="app_window_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_BINDING_JS" file="binding.js" type="BINDATA" />
+ <include name="IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS" file="context_menus_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_CONTEXT_MENUS_HANDLERS_JS" file="context_menus_handlers.js" type="BINDATA" />
+ <include name="IDR_DECLARATIVE_WEBREQUEST_CUSTOM_BINDINGS_JS" file="declarative_webrequest_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_DISPLAY_SOURCE_CUSTOM_BINDINGS_JS" file="display_source_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_CUSTOM_BINDINGS_JS" file="extension_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_GREASEMONKEY_API_JS" file="greasemonkey_api.js" type="BINDATA" />
+ <include name="IDR_I18N_CUSTOM_BINDINGS_JS" file="i18n_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_MOJO_PRIVATE_CUSTOM_BINDINGS_JS" file="mojo_private_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_PERMISSIONS_CUSTOM_BINDINGS_JS" file="permissions_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_PRINTER_PROVIDER_CUSTOM_BINDINGS_JS" file="printer_provider_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_RUNTIME_CUSTOM_BINDINGS_JS" file="runtime_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_SERVICE_WORKER_BINDINGS_JS" file="service_worker_bindings.js" type="BINDATA" />
+ <include name="IDR_WEB_REQUEST_CUSTOM_BINDINGS_JS" file="web_request_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_WEB_REQUEST_INTERNAL_CUSTOM_BINDINGS_JS" file="web_request_internal_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_WINDOW_CONTROLS_JS" file="window_controls.js" type="BINDATA" />
+ <include name="IDR_WINDOW_CONTROLS_TEMPLATE_HTML" file="window_controls_template.html" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_REQUEST_CUSTOM_BINDINGS_JS" file="guest_view/web_view/web_view_request_custom_bindings.js" type="BINDATA" />
+
+ <!-- Custom types for APIs. -->
+ <include name="IDR_STORAGE_AREA_JS" file="storage_area.js" type="BINDATA" />
+
+ <!-- Platform app support. -->
+ <include name="IDR_PLATFORM_APP_CSS" file="platform_app.css" type="BINDATA" />
+ <include name="IDR_PLATFORM_APP_JS" file="platform_app.js" type="BINDATA" />
+
+ <!-- Extension styles. -->
+ <include name="IDR_EXTENSION_FONTS_CSS" file="extension_fonts.css" type="BINDATA"/>
+
+ <!-- Media Router Mojo service and bindings. -->
+ <if expr="enable_media_router">
+ <include name="IDR_MEDIA_ROUTER_MOJOM_JS" file="${mojom_root}\chrome\browser\media\router\mojo\media_router.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_MEDIA_ROUTER_BINDINGS_JS" file="media_router_bindings.js" type="BINDATA" />
+ </if>
+ </includes>
+ <structures>
+ <!-- Extension styles. -->
+ <structure name="IDR_EXTENSION_CSS" file="extension.css" type="chrome_html" flattenhtml="true" />
+ </structures>
+ </release>
+</grit>
diff --git a/chromium/extensions/renderer/resources/greasemonkey_api.js b/chromium/extensions/renderer/resources/greasemonkey_api.js
new file mode 100644
index 00000000000..bc09911571a
--- /dev/null
+++ b/chromium/extensions/renderer/resources/greasemonkey_api.js
@@ -0,0 +1,82 @@
+// Copyright 2014 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.
+
+// -----------------------------------------------------------------------------
+// NOTE: If you change this file you need to touch renderer_resources.grd to
+// have your change take effect.
+// -----------------------------------------------------------------------------
+
+// Partial implementation of the Greasemonkey API, see:
+// http://wiki.greasespot.net/Greasemonkey_Manual:APIs
+
+function GM_addStyle(css) {
+ var parent = document.getElementsByTagName("head")[0];
+ if (!parent) {
+ parent = document.documentElement;
+ }
+ var style = document.createElement("style");
+ style.type = "text/css";
+ var textNode = document.createTextNode(css);
+ style.appendChild(textNode);
+ parent.appendChild(style);
+}
+
+function GM_xmlhttpRequest(details) {
+ function setupEvent(xhr, url, eventName, callback) {
+ xhr[eventName] = function () {
+ var isComplete = xhr.readyState == 4;
+ var responseState = {
+ responseText: xhr.responseText,
+ readyState: xhr.readyState,
+ responseHeaders: isComplete ? xhr.getAllResponseHeaders() : "",
+ status: isComplete ? xhr.status : 0,
+ statusText: isComplete ? xhr.statusText : "",
+ finalUrl: isComplete ? url : ""
+ };
+ callback(responseState);
+ };
+ }
+
+ var xhr = new XMLHttpRequest();
+ var eventNames = ["onload", "onerror", "onreadystatechange"];
+ for (var i = 0; i < eventNames.length; i++ ) {
+ var eventName = eventNames[i];
+ if (eventName in details) {
+ setupEvent(xhr, details.url, eventName, details[eventName]);
+ }
+ }
+
+ xhr.open(details.method, details.url);
+
+ if (details.overrideMimeType) {
+ xhr.overrideMimeType(details.overrideMimeType);
+ }
+ if (details.headers) {
+ for (var header in details.headers) {
+ xhr.setRequestHeader(header, details.headers[header]);
+ }
+ }
+ xhr.send(details.data ? details.data : null);
+}
+
+function GM_openInTab(url) {
+ window.open(url, "");
+}
+
+function GM_log(message) {
+ window.console.log(message);
+}
+
+(function() {
+ function generateGreasemonkeyStub(name) {
+ return function() {
+ console.log("%s is not supported.", name);
+ };
+ }
+
+ var apis = ["GM_getValue", "GM_setValue", "GM_registerMenuCommand"];
+ for (var i = 0, api; api = apis[i]; i++) {
+ window[api] = generateGreasemonkeyStub(api);
+ }
+})();
diff --git a/chromium/extensions/renderer/resources/guest_view/OWNERS b/chromium/extensions/renderer/resources/guest_view/OWNERS
new file mode 100644
index 00000000000..c7f0051d52f
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/OWNERS
@@ -0,0 +1,4 @@
+paulmeyer@chromium.org
+fsamuel@chromium.org
+lazyboy@chromium.org
+wjmaclean@chromium.org
diff --git a/chromium/extensions/renderer/resources/guest_view/app_view/app_view.js b/chromium/extensions/renderer/resources/guest_view/app_view/app_view.js
new file mode 100644
index 00000000000..a164fab1865
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/app_view/app_view.js
@@ -0,0 +1,80 @@
+// Copyright 2014 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.
+
+var DocumentNatives = requireNative('document_natives');
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+var IdGenerator = requireNative('id_generator');
+
+function AppViewImpl(appviewElement) {
+ GuestViewContainer.call(this, appviewElement, 'appview');
+
+ this.app = '';
+ this.data = '';
+}
+
+AppViewImpl.prototype.__proto__ = GuestViewContainer.prototype;
+
+AppViewImpl.VIEW_TYPE = 'AppView';
+
+// Add extra functionality to |this.element|.
+AppViewImpl.setupElement = function(proto) {
+ var apiMethods = [
+ 'connect'
+ ];
+
+ // Forward proto.foo* method calls to AppViewImpl.foo*.
+ GuestViewContainer.forwardApiMethods(proto, apiMethods);
+}
+
+AppViewImpl.prototype.getErrorNode = function() {
+ if (!this.errorNode) {
+ this.errorNode = document.createElement('div');
+ this.errorNode.innerText = 'Unable to connect to app.';
+ this.errorNode.style.position = 'absolute';
+ this.errorNode.style.left = '0px';
+ this.errorNode.style.top = '0px';
+ this.errorNode.style.width = '100%';
+ this.errorNode.style.height = '100%';
+ this.element.shadowRoot.appendChild(this.errorNode);
+ }
+ return this.errorNode;
+};
+
+AppViewImpl.prototype.buildContainerParams = function() {
+ return {
+ 'appId': this.app,
+ 'data': this.data || {}
+ };
+};
+
+AppViewImpl.prototype.connect = function(app, data, callback) {
+ if (!this.elementAttached) {
+ if (callback) {
+ callback(false);
+ }
+ return;
+ }
+
+ this.app = app;
+ this.data = data;
+
+ this.guest.destroy();
+ this.guest.create(this.buildParams(), function() {
+ if (!this.guest.getId()) {
+ var errorMsg = 'Unable to connect to app "' + app + '".';
+ window.console.warn(errorMsg);
+ this.getErrorNode().innerText = errorMsg;
+ if (callback) {
+ callback(false);
+ }
+ return;
+ }
+ this.attachWindow$();
+ if (callback) {
+ callback(true);
+ }
+ }.bind(this));
+};
+
+GuestViewContainer.registerElement(AppViewImpl);
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options.js b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options.js
new file mode 100644
index 00000000000..70e1158d403
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options.js
@@ -0,0 +1,52 @@
+// Copyright 2014 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.
+
+var ExtensionOptionsConstants =
+ require('extensionOptionsConstants').ExtensionOptionsConstants;
+var ExtensionOptionsEvents =
+ require('extensionOptionsEvents').ExtensionOptionsEvents;
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+
+function ExtensionOptionsImpl(extensionoptionsElement) {
+ GuestViewContainer.call(this, extensionoptionsElement, 'extensionoptions');
+
+ new ExtensionOptionsEvents(this);
+};
+
+ExtensionOptionsImpl.prototype.__proto__ = GuestViewContainer.prototype;
+
+ExtensionOptionsImpl.VIEW_TYPE = 'ExtensionOptions';
+
+ExtensionOptionsImpl.prototype.onElementAttached = function() {
+ this.createGuest();
+}
+
+ExtensionOptionsImpl.prototype.buildContainerParams = function() {
+ var params = {};
+ for (var i in this.attributes) {
+ params[i] = this.attributes[i].getValue();
+ }
+ return params;
+};
+
+ExtensionOptionsImpl.prototype.createGuest = function() {
+ // Destroy the old guest if one exists.
+ this.guest.destroy();
+
+ this.guest.create(this.buildParams(), function() {
+ if (!this.guest.getId()) {
+ // Fire a createfailed event here rather than in ExtensionOptionsGuest
+ // because the guest will not be created, and cannot fire an event.
+ var createFailedEvent = new Event('createfailed', { bubbles: true });
+ this.dispatchEvent(createFailedEvent);
+ } else {
+ this.attachWindow$();
+ }
+ }.bind(this));
+};
+
+GuestViewContainer.registerElement(ExtensionOptionsImpl);
+
+// Exports.
+exports.$set('ExtensionOptionsImpl', ExtensionOptionsImpl);
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_attributes.js b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_attributes.js
new file mode 100644
index 00000000000..5206f1dc713
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_attributes.js
@@ -0,0 +1,43 @@
+// Copyright 2015 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.
+
+// This module implements the attributes of the <extensionoptions> tag.
+
+var GuestViewAttributes = require('guestViewAttributes').GuestViewAttributes;
+var ExtensionOptionsConstants =
+ require('extensionOptionsConstants').ExtensionOptionsConstants;
+var ExtensionOptionsImpl = require('extensionOptions').ExtensionOptionsImpl;
+
+// -----------------------------------------------------------------------------
+// ExtensionAttribute object.
+
+// Attribute that handles extension binded to the extensionoptions.
+function ExtensionAttribute(view) {
+ GuestViewAttributes.Attribute.call(
+ this, ExtensionOptionsConstants.ATTRIBUTE_EXTENSION, view);
+}
+
+ExtensionAttribute.prototype.__proto__ =
+ GuestViewAttributes.Attribute.prototype;
+
+ExtensionAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ // Once this attribute has been set, it cannot be unset.
+ if (!newValue && oldValue) {
+ this.setValueIgnoreMutation(oldValue);
+ return;
+ }
+
+ if (!newValue || !this.elementAttached)
+ return;
+
+ this.view.createGuest();
+};
+
+// -----------------------------------------------------------------------------
+
+// Sets up all of the extensionoptions attributes.
+ExtensionOptionsImpl.prototype.setupAttributes = function() {
+ this.attributes[ExtensionOptionsConstants.ATTRIBUTE_EXTENSION] =
+ new ExtensionAttribute(this);
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_constants.js b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_constants.js
new file mode 100644
index 00000000000..c4d9692aff2
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_constants.js
@@ -0,0 +1,14 @@
+// Copyright 2015 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.
+
+// This module contains constants used in extensionoptions.
+
+// Container for the extensionview constants.
+var ExtensionOptionsConstants = {
+ // Attributes.
+ ATTRIBUTE_EXTENSION: 'extension'
+};
+
+exports.$set('ExtensionOptionsConstants',
+ $Object.freeze(ExtensionOptionsConstants));
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_events.js b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_events.js
new file mode 100644
index 00000000000..20a2f1f8a6e
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_events.js
@@ -0,0 +1,40 @@
+// Copyright 2014 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.
+
+var CreateEvent = require('guestViewEvents').CreateEvent;
+var GuestViewEvents = require('guestViewEvents').GuestViewEvents;
+
+function ExtensionOptionsEvents(extensionOptionsImpl) {
+ GuestViewEvents.call(this, extensionOptionsImpl);
+
+ // |setupEventProperty| is normally called automatically, but the
+ // 'createfailed' event is registered here because the event is fired from
+ // ExtensionOptionsImpl instead of in response to an extension event.
+ this.setupEventProperty('createfailed');
+}
+
+ExtensionOptionsEvents.prototype.__proto__ = GuestViewEvents.prototype;
+
+// A dictionary of <extensionoptions> extension events to be listened for. This
+// dictionary augments |GuestViewEvents.EVENTS| in guest_view_events.js. See the
+// documentation there for details.
+ExtensionOptionsEvents.EVENTS = {
+ 'close': {
+ evt: CreateEvent('extensionOptionsInternal.onClose')
+ },
+ 'load': {
+ evt: CreateEvent('extensionOptionsInternal.onLoad')
+ },
+ 'preferredsizechanged': {
+ evt: CreateEvent('extensionOptionsInternal.onPreferredSizeChanged'),
+ fields:['width', 'height']
+ }
+}
+
+ExtensionOptionsEvents.prototype.getEvents = function() {
+ return ExtensionOptionsEvents.EVENTS;
+};
+
+// Exports.
+exports.$set('ExtensionOptionsEvents', ExtensionOptionsEvents);
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view.js
new file mode 100644
index 00000000000..763b5541446
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view.js
@@ -0,0 +1,139 @@
+// Copyright 2015 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.
+
+// This module implements the ExtensionView <extensionview>.
+
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+var ExtensionViewConstants =
+ require('extensionViewConstants').ExtensionViewConstants;
+var ExtensionViewEvents = require('extensionViewEvents').ExtensionViewEvents;
+var ExtensionViewInternal =
+ require('extensionViewInternal').ExtensionViewInternal;
+
+function ExtensionViewImpl(extensionviewElement) {
+ GuestViewContainer.call(this, extensionviewElement, 'extensionview');
+
+ // A queue of objects in the order they should be loaded.
+ // Every load call will add the given src, as well as the resolve and reject
+ // functions. Each src will be loaded in the order they were called.
+ this.loadQueue = [];
+
+ // The current src that is loading.
+ // @type {Object<!string, function, function>}
+ this.pendingLoad = null;
+
+ new ExtensionViewEvents(this, this.viewInstanceId);
+}
+
+ExtensionViewImpl.prototype.__proto__ = GuestViewContainer.prototype;
+
+ExtensionViewImpl.VIEW_TYPE = 'ExtensionView';
+
+ExtensionViewImpl.setupElement = function(proto) {
+ var apiMethods = ExtensionViewImpl.getApiMethods();
+
+ GuestViewContainer.forwardApiMethods(proto, apiMethods);
+};
+
+ExtensionViewImpl.prototype.createGuest = function(callback) {
+ this.guest.create(this.buildParams(), function() {
+ this.attachWindow$();
+ callback();
+ }.bind(this));
+};
+
+ExtensionViewImpl.prototype.buildContainerParams = function() {
+ var params = {};
+ for (var i in this.attributes) {
+ params[i] = this.attributes[i].getValue();
+ }
+ return params;
+};
+
+ExtensionViewImpl.prototype.onElementDetached = function() {
+ this.guest.destroy();
+
+ // Reset all attributes.
+ for (var i in this.attributes) {
+ this.attributes[i].setValueIgnoreMutation();
+ }
+};
+
+// Updates src upon loadcommit.
+ExtensionViewImpl.prototype.onLoadCommit = function(url) {
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_SRC].
+ setValueIgnoreMutation(url);
+};
+
+// Loads the next pending src from |loadQueue| to the extensionview.
+ExtensionViewImpl.prototype.loadNextSrc = function() {
+ // If extensionview isn't currently loading a src, load the next src
+ // in |loadQueue|. Otherwise, do nothing.
+ if (!this.pendingLoad && this.loadQueue.length) {
+ this.pendingLoad = this.loadQueue.shift();
+ var src = this.pendingLoad.src;
+ var resolve = this.pendingLoad.resolve;
+ var reject = this.pendingLoad.reject;
+
+ // The extensionview validates the |src| twice, once in |parseSrc| and then
+ // in |loadSrc|. The |src| isn't checked directly in |loadNextSrc| for
+ // validity since the sending renderer (WebUI) is trusted.
+ ExtensionViewInternal.parseSrc(src, function(isSrcValid, extensionId) {
+ // Check if the src is valid.
+ if (!isSrcValid) {
+ reject('Failed to load: src is not valid.');
+ return;
+ }
+
+ // Destroy the current guest and create a new one if extension ID
+ // is different.
+ //
+ // This may happen if the extensionview is loads an extension page, and
+ // is then intended to load a page served from a different extension in
+ // the same part of the WebUI.
+ //
+ // The two calls may look like the following:
+ // extensionview.load('chrome-extension://firstId/page.html');
+ // extensionview.load('chrome-extension://secondId/page.html');
+ // The second time load is called, we destroy the current guest since
+ // we will be loading content from a different extension.
+ if (extensionId !=
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_EXTENSION]
+ .getValue()) {
+ this.guest.destroy();
+
+ // Update the extension and src attributes.
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_EXTENSION]
+ .setValueIgnoreMutation(extensionId);
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_SRC]
+ .setValueIgnoreMutation(src);
+
+ this.createGuest(function() {
+ if (this.guest.getId() <= 0) {
+ reject('Failed to load: guest creation failed.');
+ } else {
+ resolve('Successful load.');
+ }
+ }.bind(this));
+ } else {
+ ExtensionViewInternal.loadSrc(this.guest.getId(), src,
+ function(hasLoadSucceeded) {
+ if (!hasLoadSucceeded) {
+ reject('Failed to load.');
+ } else {
+ // Update the src attribute.
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_SRC]
+ .setValueIgnoreMutation(src);
+ resolve('Successful load.');
+ }
+ }.bind(this));
+ }
+ }.bind(this));
+ }
+};
+
+GuestViewContainer.registerElement(ExtensionViewImpl);
+
+// Exports.
+exports.$set('ExtensionViewImpl', ExtensionViewImpl);
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_api_methods.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_api_methods.js
new file mode 100644
index 00000000000..d495973072a
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_api_methods.js
@@ -0,0 +1,45 @@
+// Copyright 2015 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.
+
+// This module implements the public-facing API functions for the
+// <extensionview> tag.
+
+var ExtensionViewInternal =
+ require('extensionViewInternal').ExtensionViewInternal;
+var ExtensionViewImpl = require('extensionView').ExtensionViewImpl;
+var ExtensionViewConstants =
+ require('extensionViewConstants').ExtensionViewConstants;
+
+// An array of <extensionview>'s public-facing API methods.
+var EXTENSION_VIEW_API_METHODS = [
+ // Loads the given src into extensionview. Must be called every time the
+ // the extensionview should load a new page. This is the only way to set
+ // the extension and src attributes. Returns a promise indicating whether
+ // or not load was successful.
+ 'load'
+];
+
+// -----------------------------------------------------------------------------
+// Custom API method implementations.
+
+ExtensionViewImpl.prototype.load = function(src) {
+ return new Promise(function(resolve, reject) {
+ this.loadQueue.push({src: src, resolve: resolve, reject: reject});
+ this.loadNextSrc();
+ }.bind(this))
+ .then(function onLoadResolved() {
+ this.pendingLoad = null;
+ this.loadNextSrc();
+ }.bind(this), function onLoadRejected() {
+ this.pendingLoad = null;
+ this.loadNextSrc();
+ reject('Failed to load.');
+ }.bind(this));
+};
+
+// -----------------------------------------------------------------------------
+
+ExtensionViewImpl.getApiMethods = function() {
+ return EXTENSION_VIEW_API_METHODS;
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_attributes.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_attributes.js
new file mode 100644
index 00000000000..550fc0f5f3f
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_attributes.js
@@ -0,0 +1,55 @@
+// Copyright 2015 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.
+
+// This module implements the attributes of the <extensionview> tag.
+
+var GuestViewAttributes = require('guestViewAttributes').GuestViewAttributes;
+var ExtensionViewConstants =
+ require('extensionViewConstants').ExtensionViewConstants;
+var ExtensionViewImpl = require('extensionView').ExtensionViewImpl;
+var ExtensionViewInternal =
+ require('extensionViewInternal').ExtensionViewInternal;
+
+// -----------------------------------------------------------------------------
+// ExtensionAttribute object.
+
+// Attribute that handles the extension associated with the extensionview.
+function ExtensionAttribute(view) {
+ GuestViewAttributes.ReadOnlyAttribute.call(
+ this, ExtensionViewConstants.ATTRIBUTE_EXTENSION, view);
+}
+
+ExtensionAttribute.prototype.__proto__ =
+ GuestViewAttributes.ReadOnlyAttribute.prototype;
+
+// -----------------------------------------------------------------------------
+// SrcAttribute object.
+
+// Attribute that handles the location and navigation of the extensionview.
+// This is read only because we only want to be able to navigate to a src
+// through the load API call, which checks for URL validity and the extension
+// ID of the new src.
+function SrcAttribute(view) {
+ GuestViewAttributes.ReadOnlyAttribute.call(
+ this, ExtensionViewConstants.ATTRIBUTE_SRC, view);
+}
+
+SrcAttribute.prototype.__proto__ =
+ GuestViewAttributes.ReadOnlyAttribute.prototype;
+
+SrcAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ console.log('src is read only. Use .load(url) to navigate to a new ' +
+ 'extension page.');
+ this.setValueIgnoreMutation(oldValue);
+}
+
+// -----------------------------------------------------------------------------
+
+// Sets up all of the extensionview attributes.
+ExtensionViewImpl.prototype.setupAttributes = function() {
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_EXTENSION] =
+ new ExtensionAttribute(this);
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_SRC] =
+ new SrcAttribute(this);
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_constants.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_constants.js
new file mode 100644
index 00000000000..80934cf31e7
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_constants.js
@@ -0,0 +1,14 @@
+// Copyright 2015 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.
+
+// This module contains constants used in extensionview.
+
+// Container for the extensionview constants.
+var ExtensionViewConstants = {
+ // Attributes.
+ ATTRIBUTE_EXTENSION: 'extension',
+ ATTRIBUTE_SRC: 'src',
+};
+
+exports.$set('ExtensionViewConstants', $Object.freeze(ExtensionViewConstants));
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_events.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_events.js
new file mode 100644
index 00000000000..db674180166
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_events.js
@@ -0,0 +1,32 @@
+// Copyright 2015 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.
+
+// Event management for ExtensionView.
+
+var CreateEvent = require('guestViewEvents').CreateEvent;
+var GuestViewEvents = require('guestViewEvents').GuestViewEvents;
+
+function ExtensionViewEvents(extensionViewImpl) {
+ GuestViewEvents.call(this, extensionViewImpl);
+}
+
+ExtensionViewEvents.prototype.__proto__ = GuestViewEvents.prototype;
+
+ExtensionViewEvents.EVENTS = {
+ 'loadcommit': {
+ evt: CreateEvent('extensionViewInternal.onLoadCommit'),
+ handler: 'handleLoadCommitEvent',
+ internal: true
+ }
+};
+
+ExtensionViewEvents.prototype.getEvents = function() {
+ return ExtensionViewEvents.EVENTS;
+};
+
+ExtensionViewEvents.prototype.handleLoadCommitEvent = function(event) {
+ this.view.onLoadCommit(event.url);
+};
+
+exports.$set('ExtensionViewEvents', ExtensionViewEvents);
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_internal.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_internal.js
new file mode 100644
index 00000000000..07c6dde8c9b
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_internal.js
@@ -0,0 +1,7 @@
+// Copyright 2015 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.
+
+exports.$set(
+ 'ExtensionViewInternal',
+ require('binding').Binding.create('extensionViewInternal').generate());
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view.js b/chromium/extensions/renderer/resources/guest_view/guest_view.js
new file mode 100644
index 00000000000..1f887a746b3
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view.js
@@ -0,0 +1,355 @@
+// Copyright 2014 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.
+
+// This module implements a wrapper for a guestview that manages its
+// creation, attaching, and destruction.
+
+var CreateEvent = require('guestViewEvents').CreateEvent;
+var EventBindings = require('event_bindings');
+var GuestViewInternal =
+ require('binding').Binding.create('guestViewInternal').generate();
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+
+// Events.
+var ResizeEvent = CreateEvent('guestViewInternal.onResize');
+
+// Error messages.
+var ERROR_MSG_ALREADY_ATTACHED = 'The guest has already been attached.';
+var ERROR_MSG_ALREADY_CREATED = 'The guest has already been created.';
+var ERROR_MSG_INVALID_STATE = 'The guest is in an invalid state.';
+var ERROR_MSG_NOT_ATTACHED = 'The guest is not attached.';
+var ERROR_MSG_NOT_CREATED = 'The guest has not been created.';
+
+// Properties.
+var PROPERTY_ON_RESIZE = 'onresize';
+
+// Contains and hides the internal implementation details of |GuestView|,
+// including maintaining its state and enforcing the proper usage of its API
+// fucntions.
+function GuestViewImpl(guestView, viewType, guestInstanceId) {
+ if (guestInstanceId) {
+ this.id = guestInstanceId;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+ } else {
+ this.id = 0;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
+ }
+ this.actionQueue = [];
+ this.contentWindow = null;
+ this.guestView = guestView;
+ this.pendingAction = null;
+ this.viewType = viewType;
+ this.internalInstanceId = 0;
+
+ this.setupOnResize();
+}
+
+// Possible states.
+GuestViewImpl.GuestState = {
+ GUEST_STATE_START: 0,
+ GUEST_STATE_CREATED: 1,
+ GUEST_STATE_ATTACHED: 2
+};
+
+// Sets up the onResize property on the GuestView.
+GuestViewImpl.prototype.setupOnResize = function() {
+ $Object.defineProperty(this.guestView, PROPERTY_ON_RESIZE, {
+ get: function() {
+ return this[PROPERTY_ON_RESIZE];
+ }.bind(this),
+ set: function(value) {
+ this[PROPERTY_ON_RESIZE] = value;
+ }.bind(this),
+ enumerable: true
+ });
+
+ this.callOnResize = function(e) {
+ if (!this[PROPERTY_ON_RESIZE]) {
+ return;
+ }
+ this[PROPERTY_ON_RESIZE](e);
+ }.bind(this);
+};
+
+// Callback wrapper that is used to call the callback of the pending action (if
+// one exists), and then performs the next action in the queue.
+GuestViewImpl.prototype.handleCallback = function(callback) {
+ if (callback) {
+ callback();
+ }
+ this.pendingAction = null;
+ this.performNextAction();
+};
+
+// Perform the next action in the queue, if one exists.
+GuestViewImpl.prototype.performNextAction = function() {
+ // Make sure that there is not already an action in progress, and that there
+ // exists a queued action to perform.
+ if (!this.pendingAction && this.actionQueue.length) {
+ this.pendingAction = this.actionQueue.shift();
+ this.pendingAction();
+ }
+};
+
+// Check the current state to see if the proposed action is valid. Returns false
+// if invalid.
+GuestViewImpl.prototype.checkState = function(action) {
+ // Create an error prefix based on the proposed action.
+ var errorPrefix = 'Error calling ' + action + ': ';
+
+ // Check that the current state is valid.
+ if (!(this.state >= 0 && this.state <= 2)) {
+ window.console.error(errorPrefix + ERROR_MSG_INVALID_STATE);
+ return false;
+ }
+
+ // Map of possible errors for each action. For each action, the errors are
+ // listed for states in the order: GUEST_STATE_START, GUEST_STATE_CREATED,
+ // GUEST_STATE_ATTACHED.
+ var errors = {
+ 'attach': [ERROR_MSG_NOT_CREATED, null, ERROR_MSG_ALREADY_ATTACHED],
+ 'create': [null, ERROR_MSG_ALREADY_CREATED, ERROR_MSG_ALREADY_CREATED],
+ 'destroy': [null, null, null],
+ 'detach': [ERROR_MSG_NOT_ATTACHED, ERROR_MSG_NOT_ATTACHED, null],
+ 'setSize': [ERROR_MSG_NOT_CREATED, null, null]
+ };
+
+ // Check that the proposed action is a real action.
+ if (errors[action] == undefined) {
+ window.console.error(errorPrefix + ERROR_MSG_INVALID_ACTION);
+ return false;
+ }
+
+ // Report the error if the proposed action is found to be invalid for the
+ // current state.
+ var error;
+ if (error = errors[action][this.state]) {
+ window.console.error(errorPrefix + error);
+ return false;
+ }
+
+ return true;
+};
+
+// Returns a wrapper function for |func| with a weak reference to |this|. This
+// implementation of weakWrapper() requires a provided |viewInstanceId| since
+// GuestViewImpl does not store this ID.
+GuestViewImpl.prototype.weakWrapper = function(func, viewInstanceId) {
+ return function() {
+ var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
+ if (view && view.guest) {
+ return $Function.apply(func,
+ privates(view.guest).internal,
+ $Array.slice(arguments));
+ }
+ };
+};
+
+// Internal implementation of attach().
+GuestViewImpl.prototype.attachImpl$ = function(
+ internalInstanceId, viewInstanceId, attachParams, callback) {
+ // Check the current state.
+ if (!this.checkState('attach')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ // Callback wrapper function to store the contentWindow from the attachGuest()
+ // callback, handle potential attaching failure, register an automatic detach,
+ // and advance the queue.
+ var callbackWrapper = function(callback, contentWindow) {
+ // Check if attaching failed.
+ if (!contentWindow) {
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+ this.internalInstanceId = 0;
+ } else {
+ // Only update the contentWindow if attaching is successful.
+ this.contentWindow = contentWindow;
+ }
+
+ this.handleCallback(callback);
+ };
+
+ attachParams['instanceId'] = viewInstanceId;
+ GuestViewInternalNatives.AttachGuest(internalInstanceId,
+ this.id,
+ attachParams,
+ callbackWrapper.bind(this, callback));
+
+ this.internalInstanceId = internalInstanceId;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_ATTACHED;
+
+ // Detach automatically when the container is destroyed.
+ GuestViewInternalNatives.RegisterDestructionCallback(
+ internalInstanceId, this.weakWrapper(function() {
+ if (this.state != GuestViewImpl.GuestState.GUEST_STATE_ATTACHED ||
+ this.internalInstanceId != internalInstanceId) {
+ return;
+ }
+
+ this.internalInstanceId = 0;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+ }, viewInstanceId));
+};
+
+// Internal implementation of create().
+GuestViewImpl.prototype.createImpl$ = function(createParams, callback) {
+ // Check the current state.
+ if (!this.checkState('create')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ // Callback wrapper function to store the guestInstanceId from the
+ // createGuest() callback, handle potential creation failure, and advance the
+ // queue.
+ var callbackWrapper = function(callback, guestInfo) {
+ this.id = guestInfo.id;
+ this.contentWindow =
+ GuestViewInternalNatives.GetContentWindow(guestInfo.contentWindowId);
+
+ // Check if creation failed.
+ if (this.id === 0) {
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
+ this.contentWindow = null;
+ }
+
+ ResizeEvent.addListener(this.callOnResize, {instanceId: this.id});
+ this.handleCallback(callback);
+ };
+
+ this.sendCreateRequest(createParams, callbackWrapper.bind(this, callback));
+
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+};
+
+GuestViewImpl.prototype.sendCreateRequest = function(
+ createParams, boundCallback) {
+ GuestViewInternal.createGuest(this.viewType, createParams, boundCallback);
+};
+
+// Internal implementation of destroy().
+GuestViewImpl.prototype.destroyImpl = function(callback) {
+ // Check the current state.
+ if (!this.checkState('destroy')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ if (this.state == GuestViewImpl.GuestState.GUEST_STATE_START) {
+ // destroy() does nothing in this case.
+ this.handleCallback(callback);
+ return;
+ }
+
+ // If this guest is attached, then detach it first.
+ if (!!this.internalInstanceId) {
+ GuestViewInternalNatives.DetachGuest(this.internalInstanceId);
+ }
+
+ GuestViewInternal.destroyGuest(this.id,
+ this.handleCallback.bind(this, callback));
+
+ // Reset the state of the destroyed guest;
+ this.contentWindow = null;
+ this.id = 0;
+ this.internalInstanceId = 0;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
+ if (ResizeEvent.hasListener(this.callOnResize)) {
+ ResizeEvent.removeListener(this.callOnResize);
+ }
+};
+
+// Internal implementation of detach().
+GuestViewImpl.prototype.detachImpl = function(callback) {
+ // Check the current state.
+ if (!this.checkState('detach')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ GuestViewInternalNatives.DetachGuest(
+ this.internalInstanceId,
+ this.handleCallback.bind(this, callback));
+
+ this.internalInstanceId = 0;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+};
+
+// Internal implementation of setSize().
+GuestViewImpl.prototype.setSizeImpl = function(sizeParams, callback) {
+ // Check the current state.
+ if (!this.checkState('setSize')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ GuestViewInternal.setSize(this.id, sizeParams,
+ this.handleCallback.bind(this, callback));
+};
+
+// The exposed interface to a guestview. Exposes in its API the functions
+// attach(), create(), destroy(), and getId(). All other implementation details
+// are hidden.
+function GuestView(viewType, guestInstanceId) {
+ privates(this).internal = new GuestViewImpl(this, viewType, guestInstanceId);
+}
+
+// Attaches the guestview to the container with ID |internalInstanceId|.
+GuestView.prototype.attach = function(
+ internalInstanceId, viewInstanceId, attachParams, callback) {
+ var internal = privates(this).internal;
+ internal.actionQueue.push(internal.attachImpl$.bind(
+ internal, internalInstanceId, viewInstanceId, attachParams, callback));
+ internal.performNextAction();
+};
+
+// Creates the guestview.
+GuestView.prototype.create = function(createParams, callback) {
+ var internal = privates(this).internal;
+ internal.actionQueue.push(internal.createImpl$.bind(
+ internal, createParams, callback));
+ internal.performNextAction();
+};
+
+// Destroys the guestview. Nothing can be done with the guestview after it has
+// been destroyed.
+GuestView.prototype.destroy = function(callback) {
+ var internal = privates(this).internal;
+ internal.actionQueue.push(internal.destroyImpl.bind(internal, callback));
+ internal.performNextAction();
+};
+
+// Detaches the guestview from its container.
+// Note: This is not currently used.
+GuestView.prototype.detach = function(callback) {
+ var internal = privates(this).internal;
+ internal.actionQueue.push(internal.detachImpl.bind(internal, callback));
+ internal.performNextAction();
+};
+
+// Adjusts the guestview's sizing parameters.
+GuestView.prototype.setSize = function(sizeParams, callback) {
+ var internal = privates(this).internal;
+ internal.actionQueue.push(internal.setSizeImpl.bind(
+ internal, sizeParams, callback));
+ internal.performNextAction();
+};
+
+// Returns the contentWindow for this guestview.
+GuestView.prototype.getContentWindow = function() {
+ var internal = privates(this).internal;
+ return internal.contentWindow;
+};
+
+// Returns the ID for this guestview.
+GuestView.prototype.getId = function() {
+ var internal = privates(this).internal;
+ return internal.id;
+};
+
+// Exports
+exports.$set('GuestView', GuestView);
+exports.$set('GuestViewImpl', GuestViewImpl);
+exports.$set('ResizeEvent', ResizeEvent);
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_attributes.js b/chromium/extensions/renderer/resources/guest_view/guest_view_attributes.js
new file mode 100644
index 00000000000..6c7f711ef6c
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_attributes.js
@@ -0,0 +1,142 @@
+// Copyright 2015 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.
+
+// This module implements the base attributes of the GuestView tags.
+
+// -----------------------------------------------------------------------------
+// Attribute object.
+
+// Default implementation of a GuestView attribute.
+function Attribute(name, view) {
+ this.dirty = false;
+ this.ignoreMutation = false;
+ this.name = name;
+ this.view = view;
+
+ this.defineProperty();
+}
+
+// Retrieves and returns the attribute's value.
+Attribute.prototype.getValue = function() {
+ return this.view.element.getAttribute(this.name) || '';
+};
+
+// Retrieves and returns the attribute's value if it has been dirtied since
+// the last time this method was called. Returns null otherwise.
+Attribute.prototype.getValueIfDirty = function() {
+ if (!this.dirty)
+ return null;
+ this.dirty = false;
+ return this.getValue();
+};
+
+// Sets the attribute's value.
+Attribute.prototype.setValue = function(value) {
+ this.view.element.setAttribute(this.name, value || '');
+};
+
+// Changes the attribute's value without triggering its mutation handler.
+Attribute.prototype.setValueIgnoreMutation = function(value) {
+ this.ignoreMutation = true;
+ this.setValue(value);
+ this.ignoreMutation = false;
+};
+
+// Defines this attribute as a property on the view's element.
+Attribute.prototype.defineProperty = function() {
+ $Object.defineProperty(this.view.element, this.name, {
+ get: function() {
+ return this.getValue();
+ }.bind(this),
+ set: function(value) {
+ this.setValue(value);
+ }.bind(this),
+ enumerable: true
+ });
+};
+
+// Called when the attribute's value changes.
+Attribute.prototype.maybeHandleMutation = function(oldValue, newValue) {
+ if (this.ignoreMutation)
+ return;
+
+ this.dirty = true;
+ this.handleMutation(oldValue, newValue);
+};
+
+// Called when a change that isn't ignored occurs to the attribute's value.
+Attribute.prototype.handleMutation = function(oldValue, newValue) {};
+
+// Called when the view's element is attached to the DOM tree.
+Attribute.prototype.attach = function() {};
+
+// Called when the view's element is detached from the DOM tree.
+Attribute.prototype.detach = function() {};
+
+// -----------------------------------------------------------------------------
+// BooleanAttribute object.
+
+// An attribute that is treated as a Boolean.
+function BooleanAttribute(name, view) {
+ Attribute.call(this, name, view);
+}
+
+BooleanAttribute.prototype.__proto__ = Attribute.prototype;
+
+BooleanAttribute.prototype.getValue = function() {
+ return this.view.element.hasAttribute(this.name);
+};
+
+BooleanAttribute.prototype.setValue = function(value) {
+ if (!value) {
+ this.view.element.removeAttribute(this.name);
+ } else {
+ this.view.element.setAttribute(this.name, '');
+ }
+};
+
+// -----------------------------------------------------------------------------
+// IntegerAttribute object.
+
+// An attribute that is treated as an integer.
+function IntegerAttribute(name, view) {
+ Attribute.call(this, name, view);
+}
+
+IntegerAttribute.prototype.__proto__ = Attribute.prototype;
+
+IntegerAttribute.prototype.getValue = function() {
+ return parseInt(this.view.element.getAttribute(this.name)) || 0;
+};
+
+IntegerAttribute.prototype.setValue = function(value) {
+ this.view.element.setAttribute(this.name, parseInt(value) || 0);
+};
+
+// -----------------------------------------------------------------------------
+// ReadOnlyAttribute object.
+
+// An attribute that cannot be changed (externally). The only way to set it
+// internally is via |setValueIgnoreMutation|.
+function ReadOnlyAttribute(name, view) {
+ Attribute.call(this, name, view);
+}
+
+ReadOnlyAttribute.prototype.__proto__ = Attribute.prototype;
+
+ReadOnlyAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ this.setValueIgnoreMutation(oldValue);
+}
+
+// -----------------------------------------------------------------------------
+
+var GuestViewAttributes = {
+ Attribute: Attribute,
+ BooleanAttribute: BooleanAttribute,
+ IntegerAttribute: IntegerAttribute,
+ ReadOnlyAttribute: ReadOnlyAttribute
+};
+
+// Exports.
+exports.$set('GuestViewAttributes', GuestViewAttributes);
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_container.js b/chromium/extensions/renderer/resources/guest_view/guest_view_container.js
new file mode 100644
index 00000000000..acba9fb7b0f
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_container.js
@@ -0,0 +1,311 @@
+// Copyright 2014 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.
+
+// This module implements the shared functionality for different guestview
+// containers, such as web_view, app_view, etc.
+
+var DocumentNatives = requireNative('document_natives');
+var GuestView = require('guestView').GuestView;
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+var IdGenerator = requireNative('id_generator');
+var MessagingNatives = requireNative('messaging_natives');
+
+function GuestViewContainer(element, viewType) {
+ privates(element).internal = this;
+ this.attributes = {};
+ this.element = element;
+ this.elementAttached = false;
+ this.viewInstanceId = IdGenerator.GetNextId();
+ this.viewType = viewType;
+
+ this.setupGuestProperty();
+ this.guest = new GuestView(viewType);
+ this.setupAttributes();
+
+ privates(this).internalElement = this.createInternalElement$();
+ this.setupFocusPropagation();
+ var shadowRoot = this.element.createShadowRoot();
+ shadowRoot.appendChild(privates(this).internalElement);
+
+ GuestViewInternalNatives.RegisterView(this.viewInstanceId, this, viewType);
+}
+
+// Forward public API methods from |proto| to their internal implementations.
+GuestViewContainer.forwardApiMethods = function(proto, apiMethods) {
+ var createProtoHandler = function(m) {
+ return function(var_args) {
+ var internal = privates(this).internal;
+ return $Function.apply(internal[m], internal, arguments);
+ };
+ };
+ for (var i = 0; apiMethods[i]; ++i) {
+ proto[apiMethods[i]] = createProtoHandler(apiMethods[i]);
+ }
+};
+
+// Registers the browserplugin and guestview as custom elements once the
+// document has loaded.
+GuestViewContainer.registerElement = function(guestViewContainerType) {
+ var useCapture = true;
+ window.addEventListener('readystatechange', function listener(event) {
+ if (document.readyState == 'loading')
+ return;
+
+ registerInternalElement(guestViewContainerType.VIEW_TYPE.toLowerCase());
+ registerGuestViewElement(guestViewContainerType);
+ window.removeEventListener(event.type, listener, useCapture);
+ }, useCapture);
+};
+
+// Create the 'guest' property to track new GuestViews and always listen for
+// their resizes.
+GuestViewContainer.prototype.setupGuestProperty = function() {
+ $Object.defineProperty(this, 'guest', {
+ get: function() {
+ return privates(this).guest;
+ }.bind(this),
+ set: function(value) {
+ privates(this).guest = value;
+ if (!value) {
+ return;
+ }
+ privates(this).guest.onresize = function(e) {
+ // Dispatch the 'contentresize' event.
+ var contentResizeEvent = new Event('contentresize', { bubbles: true });
+ contentResizeEvent.oldWidth = e.oldWidth;
+ contentResizeEvent.oldHeight = e.oldHeight;
+ contentResizeEvent.newWidth = e.newWidth;
+ contentResizeEvent.newHeight = e.newHeight;
+ this.dispatchEvent(contentResizeEvent);
+ }.bind(this);
+ }.bind(this),
+ enumerable: true
+ });
+};
+
+GuestViewContainer.prototype.createInternalElement$ = function() {
+ // We create BrowserPlugin as a custom element in order to observe changes
+ // to attributes synchronously.
+ var browserPluginElement =
+ new GuestViewContainer[this.viewType + 'BrowserPlugin']();
+ privates(browserPluginElement).internal = this;
+ return browserPluginElement;
+};
+
+GuestViewContainer.prototype.setupFocusPropagation = function() {
+ if (!this.element.hasAttribute('tabIndex')) {
+ // GuestViewContainer needs a tabIndex in order to be focusable.
+ // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
+ // to allow GuestViewContainer to be focusable.
+ // See http://crbug.com/231664.
+ this.element.setAttribute('tabIndex', -1);
+ }
+ this.element.addEventListener('focus', this.weakWrapper(function(e) {
+ // Focus the BrowserPlugin when the GuestViewContainer takes focus.
+ privates(this).internalElement.focus();
+ }));
+ this.element.addEventListener('blur', this.weakWrapper(function(e) {
+ // Blur the BrowserPlugin when the GuestViewContainer loses focus.
+ privates(this).internalElement.blur();
+ }));
+};
+
+GuestViewContainer.prototype.focus = function() {
+ // Focus the internal element when focus() is called on the GuestView element.
+ privates(this).internalElement.focus();
+}
+
+GuestViewContainer.prototype.attachWindow$ = function() {
+ if (!this.internalInstanceId) {
+ return true;
+ }
+
+ this.guest.attach(this.internalInstanceId,
+ this.viewInstanceId,
+ this.buildParams());
+ return true;
+};
+
+GuestViewContainer.prototype.makeGCOwnContainer = function(internalInstanceId) {
+ MessagingNatives.BindToGC(this, function() {
+ GuestViewInternalNatives.DestroyContainer(internalInstanceId);
+ }, -1);
+};
+
+GuestViewContainer.prototype.onInternalInstanceId = function(
+ internalInstanceId) {
+ this.internalInstanceId = internalInstanceId;
+ this.makeGCOwnContainer(this.internalInstanceId);
+
+ // Track when the element resizes using the element resize callback.
+ GuestViewInternalNatives.RegisterElementResizeCallback(
+ this.internalInstanceId, this.weakWrapper(this.onElementResize));
+
+ if (!this.guest.getId()) {
+ return;
+ }
+ this.guest.attach(this.internalInstanceId,
+ this.viewInstanceId,
+ this.buildParams());
+};
+
+GuestViewContainer.prototype.handleInternalElementAttributeMutation =
+ function(name, oldValue, newValue) {
+ if (name == 'internalinstanceid' && !oldValue && !!newValue) {
+ privates(this).internalElement.removeAttribute('internalinstanceid');
+ this.onInternalInstanceId(parseInt(newValue));
+ }
+};
+
+GuestViewContainer.prototype.onElementResize = function(newWidth, newHeight) {
+ if (!this.guest.getId())
+ return;
+ this.guest.setSize({normal: {width: newWidth, height: newHeight}});
+};
+
+GuestViewContainer.prototype.buildParams = function() {
+ var params = this.buildContainerParams();
+ params['instanceId'] = this.viewInstanceId;
+ // When the GuestViewContainer is not participating in layout (display:none)
+ // then getBoundingClientRect() would report a width and height of 0.
+ // However, in the case where the GuestViewContainer has a fixed size we can
+ // use that value to initially size the guest so as to avoid a relayout of the
+ // on display:block.
+ var css = window.getComputedStyle(this.element, null);
+ var elementRect = this.element.getBoundingClientRect();
+ params['elementWidth'] = parseInt(elementRect.width) ||
+ parseInt(css.getPropertyValue('width'));
+ params['elementHeight'] = parseInt(elementRect.height) ||
+ parseInt(css.getPropertyValue('height'));
+ return params;
+};
+
+GuestViewContainer.prototype.dispatchEvent = function(event) {
+ return this.element.dispatchEvent(event);
+}
+
+// Returns a wrapper function for |func| with a weak reference to |this|.
+GuestViewContainer.prototype.weakWrapper = function(func) {
+ var viewInstanceId = this.viewInstanceId;
+ return function() {
+ var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
+ if (view) {
+ return $Function.apply(func, view, $Array.slice(arguments));
+ }
+ };
+};
+
+// Implemented by the specific view type, if needed.
+GuestViewContainer.prototype.buildContainerParams = function() { return {}; };
+GuestViewContainer.prototype.willAttachElement = function() {};
+GuestViewContainer.prototype.onElementAttached = function() {};
+GuestViewContainer.prototype.onElementDetached = function() {};
+GuestViewContainer.prototype.setupAttributes = function() {};
+
+// Registers the browser plugin <object> custom element. |viewType| is the
+// name of the specific guestview container (e.g. 'webview').
+function registerInternalElement(viewType) {
+ var proto = $Object.create(HTMLElement.prototype);
+
+ proto.createdCallback = function() {
+ this.setAttribute('type', 'application/browser-plugin');
+ this.setAttribute('id', 'browser-plugin-' + IdGenerator.GetNextId());
+ this.style.width = '100%';
+ this.style.height = '100%';
+ };
+
+ proto.attachedCallback = function() {
+ // Load the plugin immediately.
+ var unused = this.nonExistentAttribute;
+ };
+
+ proto.attributeChangedCallback = function(name, oldValue, newValue) {
+ var internal = privates(this).internal;
+ if (!internal) {
+ return;
+ }
+ internal.handleInternalElementAttributeMutation(name, oldValue, newValue);
+ };
+
+ GuestViewContainer[viewType + 'BrowserPlugin'] =
+ DocumentNatives.RegisterElement(viewType + 'browserplugin',
+ {extends: 'object', prototype: proto});
+
+ delete proto.createdCallback;
+ delete proto.attachedCallback;
+ delete proto.detachedCallback;
+ delete proto.attributeChangedCallback;
+};
+
+// Registers the guestview container as a custom element.
+// |guestViewContainerType| is the type of guestview container
+// (e.g. WebViewImpl).
+function registerGuestViewElement(guestViewContainerType) {
+ var proto = $Object.create(HTMLElement.prototype);
+
+ proto.createdCallback = function() {
+ new guestViewContainerType(this);
+ };
+
+ proto.attachedCallback = function() {
+ var internal = privates(this).internal;
+ if (!internal) {
+ return;
+ }
+ internal.elementAttached = true;
+ internal.willAttachElement();
+ internal.onElementAttached();
+ };
+
+ proto.attributeChangedCallback = function(name, oldValue, newValue) {
+ var internal = privates(this).internal;
+ if (!internal || !internal.attributes[name]) {
+ return;
+ }
+
+ // Let the changed attribute handle its own mutation.
+ internal.attributes[name].maybeHandleMutation(oldValue, newValue);
+ };
+
+ proto.detachedCallback = function() {
+ var internal = privates(this).internal;
+ if (!internal) {
+ return;
+ }
+ internal.elementAttached = false;
+ internal.internalInstanceId = 0;
+ internal.guest.destroy();
+ internal.onElementDetached();
+ };
+
+ // Override |focus| to let |internal| handle it.
+ proto.focus = function() {
+ var internal = privates(this).internal;
+ if (!internal) {
+ return;
+ }
+ internal.focus();
+ };
+
+ // Let the specific view type add extra functionality to its custom element
+ // through |proto|.
+ if (guestViewContainerType.setupElement) {
+ guestViewContainerType.setupElement(proto);
+ }
+
+ window[guestViewContainerType.VIEW_TYPE] =
+ DocumentNatives.RegisterElement(
+ guestViewContainerType.VIEW_TYPE.toLowerCase(),
+ {prototype: proto});
+
+ // Delete the callbacks so developers cannot call them and produce unexpected
+ // behavior.
+ delete proto.createdCallback;
+ delete proto.attachedCallback;
+ delete proto.detachedCallback;
+ delete proto.attributeChangedCallback;
+}
+
+// Exports.
+exports.$set('GuestViewContainer', GuestViewContainer);
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_deny.js b/chromium/extensions/renderer/resources/guest_view/guest_view_deny.js
new file mode 100644
index 00000000000..395594e77ae
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_deny.js
@@ -0,0 +1,58 @@
+// Copyright 2015 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.
+
+// This module implements the registration of guestview elements when
+// permissions are not available. These elements exist only to provide a useful
+// error message when developers attempt to use them.
+
+var DocumentNatives = requireNative('document_natives');
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+
+var ERROR_MESSAGE = 'You do not have permission to use the %1 element.' +
+ ' Be sure to declare the "%1" permission in your manifest file.';
+
+// A list of view types that will have custom elements registered if they are
+// not already registered by the time this module is loaded.
+var VIEW_TYPES = [
+ 'AppView',
+ 'ExtensionOptions',
+ 'ExtensionView',
+ 'WebView'
+];
+
+// Registers a GuestView custom element.
+function registerGuestViewElement(viewType) {
+ var proto = Object.create(HTMLElement.prototype);
+
+ proto.createdCallback = function() {
+ window.console.error(ERROR_MESSAGE.replace(/%1/g, viewType.toLowerCase()));
+ };
+
+ window[viewType] = DocumentNatives.RegisterElement(viewType.toLowerCase(),
+ {prototype: proto});
+
+ // Delete the callbacks so developers cannot call them and produce unexpected
+ // behavior.
+ delete proto.createdCallback;
+ delete proto.attachedCallback;
+ delete proto.detachedCallback;
+ delete proto.attributeChangedCallback;
+}
+
+var useCapture = true;
+window.addEventListener('readystatechange', function listener(event) {
+ if (document.readyState == 'loading')
+ return;
+
+ for (var i = 0; i != VIEW_TYPES.length; ++i) {
+ // Register the error-providing custom element only for those view types
+ // that have not already been registered. Since this module is always loaded
+ // last, all the view types that are available (i.e. have the proper
+ // permissions) will have already been registered on |window|.
+ if (!window[VIEW_TYPES[i]])
+ registerGuestViewElement(VIEW_TYPES[i]);
+ }
+
+ window.removeEventListener(event.type, listener, useCapture);
+}, useCapture);
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_events.js b/chromium/extensions/renderer/resources/guest_view/guest_view_events.js
new file mode 100644
index 00000000000..e3ccde1a6c6
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_events.js
@@ -0,0 +1,178 @@
+// Copyright 2015 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.
+
+// Event management for GuestViewContainers.
+
+var EventBindings = require('event_bindings');
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+var MessagingNatives = requireNative('messaging_natives');
+
+var CreateEvent = function(name) {
+ var eventOpts = {supportsListeners: true, supportsFilters: true};
+ return new EventBindings.Event(name, undefined, eventOpts);
+};
+
+function GuestViewEvents(view) {
+ view.events = this;
+
+ this.view = view;
+ this.on = {};
+
+ // |setupEventProperty| is normally called automatically, but these events are
+ // are registered here because they are dispatched from GuestViewContainer
+ // instead of in response to extension events.
+ this.setupEventProperty('contentresize');
+ this.setupEventProperty('resize');
+ this.setupEvents();
+}
+
+// |GuestViewEvents.EVENTS| is a dictionary of extension events to be listened
+// for, which specifies how each event should be handled. The events are
+// organized by name, and by default will be dispatched as DOM events with
+// the same name.
+// |cancelable| (default: false) specifies whether the DOM event's default
+// behavior can be canceled. If the default action associated with the event
+// is prevented, then its dispatch function will return false in its event
+// handler. The event must have a specified |handler| for this to be
+// meaningful.
+// |evt| specifies a descriptor object for the extension event. An event
+// listener will be attached to this descriptor.
+// |fields| (default: none) specifies the public-facing fields in the DOM event
+// that are accessible to developers.
+// |handler| specifies the name of a handler function to be called each time
+// that extension event is caught by its event listener. The DOM event
+// should be dispatched within this handler function (if desired). With no
+// handler function, the DOM event will be dispatched by default each time
+// the extension event is caught.
+// |internal| (default: false) specifies that the event will not be dispatched
+// as a DOM event, and will also not appear as an on* property on the view’s
+// element. A |handler| should be specified for all internal events, and
+// |fields| and |cancelable| should be left unspecified (as they are only
+// meaningful for DOM events).
+GuestViewEvents.EVENTS = {};
+
+// Attaches |listener| onto the event descriptor object |evt|, and registers it
+// to be removed once this GuestViewEvents object is garbage collected.
+GuestViewEvents.prototype.addScopedListener = function(
+ evt, listener, listenerOpts) {
+ this.listenersToBeRemoved.push({ 'evt': evt, 'listener': listener });
+ evt.addListener(listener, listenerOpts);
+};
+
+// Sets up the handling of events.
+GuestViewEvents.prototype.setupEvents = function() {
+ // An array of registerd event listeners that should be removed when this
+ // GuestViewEvents is garbage collected.
+ this.listenersToBeRemoved = [];
+ MessagingNatives.BindToGC(this, function(listenersToBeRemoved) {
+ for (var i = 0; i != listenersToBeRemoved.length; ++i) {
+ listenersToBeRemoved[i].evt.removeListener(
+ listenersToBeRemoved[i].listener);
+ listenersToBeRemoved[i] = null;
+ }
+ }.bind(undefined, this.listenersToBeRemoved), -1 /* portId */);
+
+ // Set up the GuestView events.
+ for (var eventName in GuestViewEvents.EVENTS) {
+ this.setupEvent(eventName, GuestViewEvents.EVENTS[eventName]);
+ }
+
+ // Set up the derived view's events.
+ var events = this.getEvents();
+ for (var eventName in events) {
+ this.setupEvent(eventName, events[eventName]);
+ }
+};
+
+// Sets up the handling of the |eventName| event.
+GuestViewEvents.prototype.setupEvent = function(eventName, eventInfo) {
+ if (!eventInfo.internal) {
+ this.setupEventProperty(eventName);
+ }
+
+ var listenerOpts = { instanceId: this.view.viewInstanceId };
+ if (eventInfo.handler) {
+ this.addScopedListener(eventInfo.evt, this.weakWrapper(function(e) {
+ this[eventInfo.handler](e, eventName);
+ }), listenerOpts);
+ return;
+ }
+
+ // Internal events are not dispatched as DOM events.
+ if (eventInfo.internal) {
+ return;
+ }
+
+ this.addScopedListener(eventInfo.evt, this.weakWrapper(function(e) {
+ var domEvent = this.makeDomEvent(e, eventName);
+ this.view.dispatchEvent(domEvent);
+ }), listenerOpts);
+};
+
+// Constructs a DOM event based on the info for the |eventName| event provided
+// in either |GuestViewEvents.EVENTS| or getEvents().
+GuestViewEvents.prototype.makeDomEvent = function(event, eventName) {
+ var eventInfo =
+ GuestViewEvents.EVENTS[eventName] || this.getEvents()[eventName];
+
+ // Internal events are not dispatched as DOM events.
+ if (eventInfo.internal) {
+ return null;
+ }
+
+ var details = { bubbles: true };
+ if (eventInfo.cancelable) {
+ details.cancelable = true;
+ }
+ var domEvent = new Event(eventName, details);
+ if (eventInfo.fields) {
+ $Array.forEach(eventInfo.fields, function(field) {
+ if (event[field] !== undefined) {
+ domEvent[field] = event[field];
+ }
+ }.bind(this));
+ }
+
+ return domEvent;
+};
+
+// Adds an 'on<event>' property on the view, which can be used to set/unset
+// an event handler.
+GuestViewEvents.prototype.setupEventProperty = function(eventName) {
+ var propertyName = 'on' + eventName.toLowerCase();
+ $Object.defineProperty(this.view.element, propertyName, {
+ get: function() {
+ return this.on[propertyName];
+ }.bind(this),
+ set: function(value) {
+ if (this.on[propertyName]) {
+ this.view.element.removeEventListener(eventName, this.on[propertyName]);
+ }
+ this.on[propertyName] = value;
+ if (value) {
+ this.view.element.addEventListener(eventName, value);
+ }
+ }.bind(this),
+ enumerable: true
+ });
+};
+
+// returns a wrapper for |func| with a weak reference to |this|.
+GuestViewEvents.prototype.weakWrapper = function(func) {
+ var viewInstanceId = this.view.viewInstanceId;
+ return function() {
+ var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
+ if (!view) {
+ return;
+ }
+ return $Function.apply(func, view.events, $Array.slice(arguments));
+ };
+};
+
+// Implemented by the derived event manager, if one exists.
+GuestViewEvents.prototype.getEvents = function() { return {}; };
+
+// Exports.
+exports.$set('GuestViewEvents', GuestViewEvents);
+exports.$set('CreateEvent', CreateEvent);
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_iframe.js b/chromium/extensions/renderer/resources/guest_view/guest_view_iframe.js
new file mode 100644
index 00000000000..a73de791a98
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_iframe.js
@@ -0,0 +1,108 @@
+// Copyright 2015 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.
+
+// --site-per-process overrides for guest_view.js.
+
+var GuestView = require('guestView').GuestView;
+var GuestViewImpl = require('guestView').GuestViewImpl;
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+var ResizeEvent = require('guestView').ResizeEvent;
+
+var getIframeContentWindow = function(viewInstanceId) {
+ var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
+ if (!view)
+ return null;
+
+ var internalIframeElement = privates(view).internalElement;
+ if (internalIframeElement)
+ return internalIframeElement.contentWindow;
+
+ return null;
+};
+
+// Internal implementation of attach().
+GuestViewImpl.prototype.attachImpl$ = function(
+ internalInstanceId, viewInstanceId, attachParams, callback) {
+ var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
+ if (!view.elementAttached) {
+ // Defer the attachment until the <webview> element is attached.
+ view.deferredAttachCallback = this.attachImpl$.bind(
+ this, internalInstanceId, viewInstanceId, attachParams, callback);
+ return;
+ };
+
+ // Check the current state.
+ if (!this.checkState('attach')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ // Callback wrapper function to store the contentWindow from the attachGuest()
+ // callback, handle potential attaching failure, register an automatic detach,
+ // and advance the queue.
+ var callbackWrapper = function(callback, contentWindow) {
+ // Check if attaching failed.
+ contentWindow = getIframeContentWindow(viewInstanceId);
+ if (!contentWindow) {
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+ this.internalInstanceId = 0;
+ } else {
+ // Only update the contentWindow if attaching is successful.
+ this.contentWindow = contentWindow;
+ }
+
+ this.handleCallback(callback);
+ };
+
+ attachParams['instanceId'] = viewInstanceId;
+ var contentWindow = getIframeContentWindow(viewInstanceId);
+ // |contentWindow| is used to retrieve the RenderFrame in cpp.
+ GuestViewInternalNatives.AttachIframeGuest(
+ internalInstanceId, this.id, attachParams, contentWindow,
+ callbackWrapper.bind(this, callback));
+
+ this.internalInstanceId = internalInstanceId;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_ATTACHED;
+
+ // Detach automatically when the container is destroyed.
+ GuestViewInternalNatives.RegisterDestructionCallback(
+ internalInstanceId, this.weakWrapper(function() {
+ if (this.state != GuestViewImpl.GuestState.GUEST_STATE_ATTACHED ||
+ this.internalInstanceId != internalInstanceId) {
+ return;
+ }
+
+ this.internalInstanceId = 0;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+ }, viewInstanceId));
+};
+
+// Internal implementation of create().
+GuestViewImpl.prototype.createImpl$ = function(createParams, callback) {
+ // Check the current state.
+ if (!this.checkState('create')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ // Callback wrapper function to store the guestInstanceId from the
+ // createGuest() callback, handle potential creation failure, and advance the
+ // queue.
+ var callbackWrapper = function(callback, guestInfo) {
+ this.id = guestInfo.id;
+
+ // Check if creation failed.
+ if (this.id === 0) {
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
+ this.contentWindow = null;
+ }
+
+ ResizeEvent.addListener(this.callOnResize, {instanceId: this.id});
+ this.handleCallback(callback);
+ };
+
+ this.sendCreateRequest(createParams, callbackWrapper.bind(this, callback));
+
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_iframe_container.js b/chromium/extensions/renderer/resources/guest_view/guest_view_iframe_container.js
new file mode 100644
index 00000000000..4cd628be8f7
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_iframe_container.js
@@ -0,0 +1,30 @@
+// Copyright 2015 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.
+
+// --site-per-process overrides for guest_view_container.js
+
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+var IdGenerator = requireNative('id_generator');
+
+GuestViewContainer.prototype.createInternalElement$ = function() {
+ var iframeElement = document.createElement('iframe');
+ iframeElement.style.width = '100%';
+ iframeElement.style.height = '100%';
+ privates(iframeElement).internal = this;
+ return iframeElement;
+};
+
+GuestViewContainer.prototype.attachWindow$ = function() {
+ var generatedId = IdGenerator.GetNextId();
+ // Generate an instance id for the container.
+ this.onInternalInstanceId(generatedId);
+ return true;
+};
+
+GuestViewContainer.prototype.willAttachElement = function () {
+ if (this.deferredAttachCallback) {
+ this.deferredAttachCallback();
+ this.deferredAttachCallback = null;
+ }
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view.js
new file mode 100644
index 00000000000..b5d08c19ad5
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view.js
@@ -0,0 +1,235 @@
+// 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.
+
+// This module implements WebView (<webview>) as a custom element that wraps a
+// BrowserPlugin object element. The object element is hidden within
+// the shadow DOM of the WebView element.
+
+var DocumentNatives = requireNative('document_natives');
+var GuestView = require('guestView').GuestView;
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+var WebViewConstants = require('webViewConstants').WebViewConstants;
+var WebViewEvents = require('webViewEvents').WebViewEvents;
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+
+// Represents the internal state of <webview>.
+function WebViewImpl(webviewElement) {
+ GuestViewContainer.call(this, webviewElement, 'webview');
+ this.cachedZoom = 1;
+ this.setupElementProperties();
+ new WebViewEvents(this, this.viewInstanceId);
+}
+
+WebViewImpl.prototype.__proto__ = GuestViewContainer.prototype;
+
+WebViewImpl.VIEW_TYPE = 'WebView';
+
+// Add extra functionality to |this.element|.
+WebViewImpl.setupElement = function(proto) {
+ // Public-facing API methods.
+ var apiMethods = WebViewImpl.getApiMethods();
+
+ // Add the experimental API methods, if available.
+ var experimentalApiMethods = WebViewImpl.maybeGetExperimentalApiMethods();
+ apiMethods = $Array.concat(apiMethods, experimentalApiMethods);
+
+ // Create default implementations for undefined API methods.
+ var createDefaultApiMethod = function(m) {
+ return function(var_args) {
+ if (!this.guest.getId()) {
+ return false;
+ }
+ var args = $Array.concat([this.guest.getId()], $Array.slice(arguments));
+ $Function.apply(WebViewInternal[m], null, args);
+ return true;
+ };
+ };
+ for (var i = 0; i != apiMethods.length; ++i) {
+ if (WebViewImpl.prototype[apiMethods[i]] == undefined) {
+ WebViewImpl.prototype[apiMethods[i]] =
+ createDefaultApiMethod(apiMethods[i]);
+ }
+ }
+
+ // Forward proto.foo* method calls to WebViewImpl.foo*.
+ GuestViewContainer.forwardApiMethods(proto, apiMethods);
+};
+
+// Initiates navigation once the <webview> element is attached to the DOM.
+WebViewImpl.prototype.onElementAttached = function() {
+ // Mark all attributes as dirty on attachment.
+ for (var i in this.attributes) {
+ this.attributes[i].dirty = true;
+ }
+ for (var i in this.attributes) {
+ this.attributes[i].attach();
+ }
+};
+
+// Resets some state upon detaching <webview> element from the DOM.
+WebViewImpl.prototype.onElementDetached = function() {
+ this.guest.destroy();
+ for (var i in this.attributes) {
+ this.attributes[i].dirty = false;
+ }
+ for (var i in this.attributes) {
+ this.attributes[i].detach();
+ }
+};
+
+// Sets the <webview>.request property.
+WebViewImpl.prototype.setRequestPropertyOnWebViewElement = function(request) {
+ Object.defineProperty(
+ this.element,
+ 'request',
+ {
+ value: request,
+ enumerable: true
+ }
+ );
+};
+
+WebViewImpl.prototype.setupElementProperties = function() {
+ // We cannot use {writable: true} property descriptor because we want a
+ // dynamic getter value.
+ Object.defineProperty(this.element, 'contentWindow', {
+ get: function() {
+ return this.guest.getContentWindow();
+ }.bind(this),
+ // No setter.
+ enumerable: true
+ });
+};
+
+WebViewImpl.prototype.onSizeChanged = function(webViewEvent) {
+ var newWidth = webViewEvent.newWidth;
+ var newHeight = webViewEvent.newHeight;
+
+ var element = this.element;
+
+ var width = element.offsetWidth;
+ var height = element.offsetHeight;
+
+ // Check the current bounds to make sure we do not resize <webview>
+ // outside of current constraints.
+ var maxWidth = this.attributes[
+ WebViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || width;
+ var minWidth = this.attributes[
+ WebViewConstants.ATTRIBUTE_MINWIDTH].getValue() || width;
+ var maxHeight = this.attributes[
+ WebViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || height;
+ var minHeight = this.attributes[
+ WebViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || height;
+
+ minWidth = Math.min(minWidth, maxWidth);
+ minHeight = Math.min(minHeight, maxHeight);
+
+ if (!this.attributes[WebViewConstants.ATTRIBUTE_AUTOSIZE].getValue() ||
+ (newWidth >= minWidth &&
+ newWidth <= maxWidth &&
+ newHeight >= minHeight &&
+ newHeight <= maxHeight)) {
+ element.style.width = newWidth + 'px';
+ element.style.height = newHeight + 'px';
+ // Only fire the DOM event if the size of the <webview> has actually
+ // changed.
+ this.dispatchEvent(webViewEvent);
+ }
+};
+
+WebViewImpl.prototype.createGuest = function() {
+ this.guest.create(this.buildParams(), function() {
+ this.attachWindow$();
+ }.bind(this));
+};
+
+WebViewImpl.prototype.onFrameNameChanged = function(name) {
+ this.attributes[WebViewConstants.ATTRIBUTE_NAME].setValueIgnoreMutation(name);
+};
+
+// Updates state upon loadcommit.
+WebViewImpl.prototype.onLoadCommit = function(
+ baseUrlForDataUrl, currentEntryIndex, entryCount,
+ processId, url, isTopLevel) {
+ this.baseUrlForDataUrl = baseUrlForDataUrl;
+ this.currentEntryIndex = currentEntryIndex;
+ this.entryCount = entryCount;
+ this.processId = processId;
+ if (isTopLevel) {
+ // Touching the src attribute triggers a navigation. To avoid
+ // triggering a page reload on every guest-initiated navigation,
+ // we do not handle this mutation.
+ this.attributes[
+ WebViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation(url);
+ }
+};
+
+WebViewImpl.prototype.onAttach = function(storagePartitionId) {
+ this.attributes[WebViewConstants.ATTRIBUTE_PARTITION].setValueIgnoreMutation(
+ storagePartitionId);
+};
+
+WebViewImpl.prototype.buildContainerParams = function() {
+ var params = { 'initialZoomFactor': this.cachedZoomFactor,
+ 'userAgentOverride': this.userAgentOverride };
+ for (var i in this.attributes) {
+ var value = this.attributes[i].getValueIfDirty();
+ if (value)
+ params[i] = value;
+ }
+ return params;
+};
+
+WebViewImpl.prototype.attachWindow$ = function(opt_guestInstanceId) {
+ // If |opt_guestInstanceId| was provided, then a different existing guest is
+ // being attached to this webview, and the current one will get destroyed.
+ if (opt_guestInstanceId) {
+ if (this.guest.getId() == opt_guestInstanceId) {
+ return true;
+ }
+ this.guest.destroy();
+ this.guest = new GuestView('webview', opt_guestInstanceId);
+ }
+
+ return GuestViewContainer.prototype.attachWindow$.call(this);
+};
+
+// Shared implementation of executeScript() and insertCSS().
+WebViewImpl.prototype.executeCode = function(func, args) {
+ if (!this.guest.getId()) {
+ window.console.error(WebViewConstants.ERROR_MSG_CANNOT_INJECT_SCRIPT);
+ return false;
+ }
+
+ var webviewSrc = this.attributes[WebViewConstants.ATTRIBUTE_SRC].getValue();
+ if (this.baseUrlForDataUrl) {
+ webviewSrc = this.baseUrlForDataUrl;
+ }
+
+ args = $Array.concat([this.guest.getId(), webviewSrc],
+ $Array.slice(args));
+ $Function.apply(func, null, args);
+ return true;
+}
+
+// Requests the <webview> element wihtin the embedder to enter fullscreen.
+WebViewImpl.prototype.makeElementFullscreen = function() {
+ GuestViewInternalNatives.RunWithGesture(function() {
+ this.element.webkitRequestFullScreen();
+ }.bind(this));
+};
+
+// Implemented when the ChromeWebView API is available.
+WebViewImpl.prototype.maybeSetupContextMenus = function() {};
+
+// Implemented when the experimental WebView API is available.
+WebViewImpl.maybeGetExperimentalApiMethods = function() {
+ return [];
+};
+
+GuestViewContainer.registerElement(WebViewImpl);
+
+// Exports.
+exports.$set('WebViewImpl', WebViewImpl);
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js
new file mode 100644
index 00000000000..5f4bf2c0607
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js
@@ -0,0 +1,296 @@
+// Copyright 2014 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.
+
+// This module implements helper objects for the dialog, newwindow, and
+// permissionrequest <webview> events.
+
+var MessagingNatives = requireNative('messaging_natives');
+var WebViewConstants = require('webViewConstants').WebViewConstants;
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+
+var PERMISSION_TYPES = ['media',
+ 'geolocation',
+ 'pointerLock',
+ 'download',
+ 'loadplugin',
+ 'filesystem',
+ 'fullscreen'];
+
+// -----------------------------------------------------------------------------
+// WebViewActionRequest object.
+
+// Default partial implementation of a webview action request.
+function WebViewActionRequest(webViewImpl, event, webViewEvent, interfaceName) {
+ this.webViewImpl = webViewImpl;
+ this.event = event;
+ this.webViewEvent = webViewEvent;
+ this.interfaceName = interfaceName;
+ this.guestInstanceId = this.webViewImpl.guest.getId();
+ this.requestId = event.requestId;
+ this.actionTaken = false;
+
+ // Add on the request information specific to the request type.
+ for (var infoName in this.event.requestInfo) {
+ this.event[infoName] = this.event.requestInfo[infoName];
+ this.webViewEvent[infoName] = this.event.requestInfo[infoName];
+ }
+}
+
+// Performs the default action for the request.
+WebViewActionRequest.prototype.defaultAction = function() {
+ // Do nothing if the action has already been taken or the requester is
+ // already gone (in which case its guestInstanceId will be stale).
+ if (this.actionTaken ||
+ this.guestInstanceId != this.webViewImpl.guest.getId()) {
+ return;
+ }
+
+ this.actionTaken = true;
+ WebViewInternal.setPermission(this.guestInstanceId, this.requestId,
+ 'default', '', function(allowed) {
+ if (allowed) {
+ return;
+ }
+ this.showWarningMessage();
+ }.bind(this));
+};
+
+// Called to handle the action request's event.
+WebViewActionRequest.prototype.handleActionRequestEvent = function() {
+ // Construct the interface object and attach it to |webViewEvent|.
+ var request = this.getInterfaceObject();
+ this.webViewEvent[this.interfaceName] = request;
+
+ var defaultPrevented = !this.webViewImpl.dispatchEvent(this.webViewEvent);
+ // Set |webViewEvent| to null to break the circular reference to |request| so
+ // that the garbage collector can eventually collect it.
+ this.webViewEvent = null;
+ if (this.actionTaken) {
+ return;
+ }
+
+ if (defaultPrevented) {
+ // Track the lifetime of |request| with the garbage collector.
+ var portId = -1; // (hack) there is no Extension Port to release
+ MessagingNatives.BindToGC(request, this.defaultAction.bind(this), portId);
+ } else {
+ this.defaultAction();
+ }
+};
+
+// Displays a warning message when an action request is blocked by default.
+WebViewActionRequest.prototype.showWarningMessage = function() {
+ window.console.warn(this.WARNING_MSG_REQUEST_BLOCKED);
+};
+
+// This function ensures that each action is taken at most once.
+WebViewActionRequest.prototype.validateCall = function() {
+ if (this.actionTaken) {
+ throw new Error(this.ERROR_MSG_ACTION_ALREADY_TAKEN);
+ }
+ this.actionTaken = true;
+};
+
+// The following are implemented by the specific action request.
+
+// Returns the interface object for this action request.
+WebViewActionRequest.prototype.getInterfaceObject = undefined;
+
+// Error/warning messages.
+WebViewActionRequest.prototype.ERROR_MSG_ACTION_ALREADY_TAKEN = undefined;
+WebViewActionRequest.prototype.WARNING_MSG_REQUEST_BLOCKED = undefined;
+
+// -----------------------------------------------------------------------------
+// Dialog object.
+
+// Represents a dialog box request (e.g. alert()).
+function Dialog(webViewImpl, event, webViewEvent) {
+ WebViewActionRequest.call(this, webViewImpl, event, webViewEvent, 'dialog');
+
+ this.handleActionRequestEvent();
+}
+
+Dialog.prototype.__proto__ = WebViewActionRequest.prototype;
+
+Dialog.prototype.getInterfaceObject = function() {
+ return {
+ ok: function(user_input) {
+ this.validateCall();
+ user_input = user_input || '';
+ WebViewInternal.setPermission(
+ this.guestInstanceId, this.requestId, 'allow', user_input);
+ }.bind(this),
+ cancel: function() {
+ this.validateCall();
+ WebViewInternal.setPermission(
+ this.guestInstanceId, this.requestId, 'deny');
+ }.bind(this)
+ };
+};
+
+Dialog.prototype.showWarningMessage = function() {
+ var VOWELS = ['a', 'e', 'i', 'o', 'u'];
+ var dialogType = this.event.messageType;
+ var article = (VOWELS.indexOf(dialogType.charAt(0)) >= 0) ? 'An' : 'A';
+ this.WARNING_MSG_REQUEST_BLOCKED = this.WARNING_MSG_REQUEST_BLOCKED.
+ replace('%1', article).replace('%2', dialogType);
+ window.console.warn(this.WARNING_MSG_REQUEST_BLOCKED);
+};
+
+Dialog.prototype.ERROR_MSG_ACTION_ALREADY_TAKEN =
+ WebViewConstants.ERROR_MSG_DIALOG_ACTION_ALREADY_TAKEN;
+Dialog.prototype.WARNING_MSG_REQUEST_BLOCKED =
+ WebViewConstants.WARNING_MSG_DIALOG_REQUEST_BLOCKED;
+
+// -----------------------------------------------------------------------------
+// NewWindow object.
+
+// Represents a new window request.
+function NewWindow(webViewImpl, event, webViewEvent) {
+ WebViewActionRequest.call(this, webViewImpl, event, webViewEvent, 'window');
+
+ this.handleActionRequestEvent();
+}
+
+NewWindow.prototype.__proto__ = WebViewActionRequest.prototype;
+
+NewWindow.prototype.getInterfaceObject = function() {
+ return {
+ attach: function(webview) {
+ this.validateCall();
+ if (!webview || !webview.tagName || webview.tagName != 'WEBVIEW') {
+ throw new Error(ERROR_MSG_WEBVIEW_EXPECTED);
+ }
+
+ var webViewImpl = privates(webview).internal;
+ // Update the partition.
+ if (this.event.partition) {
+ webViewImpl.onAttach(this.event.partition);
+ }
+
+ var attached = webViewImpl.attachWindow$(this.event.windowId);
+ if (!attached) {
+ window.console.error(ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH);
+ }
+
+ if (this.guestInstanceId != this.webViewImpl.guest.getId()) {
+ // If the opener is already gone, then its guestInstanceId will be
+ // stale.
+ return;
+ }
+
+ // If the object being passed into attach is not a valid <webview>
+ // then we will fail and it will be treated as if the new window
+ // was rejected. The permission API plumbing is used here to clean
+ // up the state created for the new window if attaching fails.
+ WebViewInternal.setPermission(this.guestInstanceId, this.requestId,
+ attached ? 'allow' : 'deny');
+ }.bind(this),
+ discard: function() {
+ this.validateCall();
+ if (!this.guestInstanceId) {
+ // If the opener is already gone, then we won't have its
+ // guestInstanceId.
+ return;
+ }
+ WebViewInternal.setPermission(
+ this.guestInstanceId, this.requestId, 'deny');
+ }.bind(this)
+ };
+};
+
+NewWindow.prototype.ERROR_MSG_ACTION_ALREADY_TAKEN =
+ WebViewConstants.ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN;
+NewWindow.prototype.WARNING_MSG_REQUEST_BLOCKED =
+ WebViewConstants.WARNING_MSG_NEWWINDOW_REQUEST_BLOCKED;
+
+// -----------------------------------------------------------------------------
+// PermissionRequest object.
+
+// Represents a permission request (e.g. to access the filesystem).
+function PermissionRequest(webViewImpl, event, webViewEvent) {
+ WebViewActionRequest.call(this, webViewImpl, event, webViewEvent, 'request');
+
+ if (!this.validPermissionCheck()) {
+ return;
+ }
+
+ this.handleActionRequestEvent();
+}
+
+PermissionRequest.prototype.__proto__ = WebViewActionRequest.prototype;
+
+PermissionRequest.prototype.allow = function() {
+ this.validateCall();
+ WebViewInternal.setPermission(this.guestInstanceId, this.requestId, 'allow');
+};
+
+PermissionRequest.prototype.deny = function() {
+ this.validateCall();
+ WebViewInternal.setPermission(this.guestInstanceId, this.requestId, 'deny');
+};
+
+PermissionRequest.prototype.getInterfaceObject = function() {
+ var request = {
+ allow: this.allow.bind(this),
+ deny: this.deny.bind(this)
+ };
+
+ // Add on the request information specific to the request type.
+ for (var infoName in this.event.requestInfo) {
+ request[infoName] = this.event.requestInfo[infoName];
+ }
+
+ return $Object.freeze(request);
+};
+
+PermissionRequest.prototype.showWarningMessage = function() {
+ window.console.warn(
+ this.WARNING_MSG_REQUEST_BLOCKED.replace('%1', this.event.permission));
+};
+
+// Checks that the requested permission is valid. Returns true if valid.
+PermissionRequest.prototype.validPermissionCheck = function() {
+ if (PERMISSION_TYPES.indexOf(this.event.permission) < 0) {
+ // The permission type is not allowed. Trigger the default response.
+ this.defaultAction();
+ return false;
+ }
+ return true;
+};
+
+PermissionRequest.prototype.ERROR_MSG_ACTION_ALREADY_TAKEN =
+ WebViewConstants.ERROR_MSG_PERMISSION_ACTION_ALREADY_TAKEN;
+PermissionRequest.prototype.WARNING_MSG_REQUEST_BLOCKED =
+ WebViewConstants.WARNING_MSG_PERMISSION_REQUEST_BLOCKED;
+
+// -----------------------------------------------------------------------------
+
+// FullscreenPermissionRequest object.
+
+// Represents a fullscreen permission request.
+function FullscreenPermissionRequest(webViewImpl, event, webViewEvent) {
+ PermissionRequest.call(this, webViewImpl, event, webViewEvent);
+}
+
+FullscreenPermissionRequest.prototype.__proto__ = PermissionRequest.prototype;
+
+FullscreenPermissionRequest.prototype.allow = function() {
+ PermissionRequest.prototype.allow.call(this);
+ // Now make the <webview> element go fullscreen.
+ this.webViewImpl.makeElementFullscreen();
+};
+
+// -----------------------------------------------------------------------------
+
+var WebViewActionRequests = {
+ WebViewActionRequest: WebViewActionRequest,
+ Dialog: Dialog,
+ NewWindow: NewWindow,
+ PermissionRequest: PermissionRequest,
+ FullscreenPermissionRequest: FullscreenPermissionRequest
+};
+
+// Exports.
+exports.$set('WebViewActionRequests', WebViewActionRequests);
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_api_methods.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_api_methods.js
new file mode 100644
index 00000000000..a28741b7430
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_api_methods.js
@@ -0,0 +1,204 @@
+// Copyright 2014 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.
+
+// This module implements the public-facing API functions for the <webview> tag.
+
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+var WebViewImpl = require('webView').WebViewImpl;
+
+// An array of <webview>'s public-facing API methods. Methods without custom
+// implementations will be given default implementations that call into the
+// internal API method with the same name in |WebViewInternal|. For example, a
+// method called 'someApiMethod' would be given the following default
+// implementation:
+//
+// WebViewImpl.prototype.someApiMethod = function(var_args) {
+// if (!this.guest.getId()) {
+// return false;
+// }
+// var args = $Array.concat([this.guest.getId()], $Array.slice(arguments));
+// $Function.apply(WebViewInternal.someApiMethod, null, args);
+// return true;
+// };
+//
+// These default implementations come from createDefaultApiMethod() in
+// web_view.js.
+var WEB_VIEW_API_METHODS = [
+ // Add content scripts for the guest page.
+ 'addContentScripts',
+
+ // Navigates to the previous history entry.
+ 'back',
+
+ // Returns whether there is a previous history entry to navigate to.
+ 'canGoBack',
+
+ // Returns whether there is a subsequent history entry to navigate to.
+ 'canGoForward',
+
+ // Clears browsing data for the WebView partition.
+ 'clearData',
+
+ // Injects JavaScript code into the guest page.
+ 'executeScript',
+
+ // Initiates a find-in-page request.
+ 'find',
+
+ // Navigates to the subsequent history entry.
+ 'forward',
+
+ // Returns Chrome's internal process ID for the guest web page's current
+ // process.
+ 'getProcessId',
+
+ // Returns the user agent string used by the webview for guest page requests.
+ 'getUserAgent',
+
+ // Gets the current zoom factor.
+ 'getZoom',
+
+ // Gets the current zoom mode of the webview.
+ 'getZoomMode',
+
+ // Navigates to a history entry using a history index relative to the current
+ // navigation.
+ 'go',
+
+ // Injects CSS into the guest page.
+ 'insertCSS',
+
+ // Indicates whether or not the webview's user agent string has been
+ // overridden.
+ 'isUserAgentOverridden',
+
+ // Loads a data URL with a specified base URL used for relative links.
+ // Optionally, a virtual URL can be provided to be shown to the user instead
+ // of the data URL.
+ 'loadDataWithBaseUrl',
+
+ // Prints the contents of the webview.
+ 'print',
+
+ // Removes content scripts for the guest page.
+ 'removeContentScripts',
+
+ // Reloads the current top-level page.
+ 'reload',
+
+ // Override the user agent string used by the webview for guest page requests.
+ 'setUserAgentOverride',
+
+ // Changes the zoom factor of the page.
+ 'setZoom',
+
+ // Changes the zoom mode of the webview.
+ 'setZoomMode',
+
+ // Stops loading the current navigation if one is in progress.
+ 'stop',
+
+ // Ends the current find session.
+ 'stopFinding',
+
+ // Forcibly kills the guest web page's renderer process.
+ 'terminate'
+];
+
+// -----------------------------------------------------------------------------
+// Custom API method implementations.
+
+WebViewImpl.prototype.addContentScripts = function(rules) {
+ return WebViewInternal.addContentScripts(this.viewInstanceId, rules);
+};
+
+WebViewImpl.prototype.back = function(callback) {
+ return this.go(-1, callback);
+};
+
+WebViewImpl.prototype.canGoBack = function() {
+ return this.entryCount > 1 && this.currentEntryIndex > 0;
+};
+
+WebViewImpl.prototype.canGoForward = function() {
+ return this.currentEntryIndex >= 0 &&
+ this.currentEntryIndex < (this.entryCount - 1);
+};
+
+WebViewImpl.prototype.executeScript = function(var_args) {
+ return this.executeCode(WebViewInternal.executeScript,
+ $Array.slice(arguments));
+};
+
+WebViewImpl.prototype.forward = function(callback) {
+ return this.go(1, callback);
+};
+
+WebViewImpl.prototype.getProcessId = function() {
+ return this.processId;
+};
+
+WebViewImpl.prototype.getUserAgent = function() {
+ return this.userAgentOverride || navigator.userAgent;
+};
+
+WebViewImpl.prototype.insertCSS = function(var_args) {
+ return this.executeCode(WebViewInternal.insertCSS, $Array.slice(arguments));
+};
+
+WebViewImpl.prototype.isUserAgentOverridden = function() {
+ return !!this.userAgentOverride &&
+ this.userAgentOverride != navigator.userAgent;
+};
+
+WebViewImpl.prototype.loadDataWithBaseUrl = function(
+ dataUrl, baseUrl, virtualUrl) {
+ if (!this.guest.getId()) {
+ return;
+ }
+ WebViewInternal.loadDataWithBaseUrl(
+ this.guest.getId(), dataUrl, baseUrl, virtualUrl, function() {
+ // Report any errors.
+ if (chrome.runtime.lastError != undefined) {
+ window.console.error(
+ 'Error while running webview.loadDataWithBaseUrl: ' +
+ chrome.runtime.lastError.message);
+ }
+ });
+};
+
+WebViewImpl.prototype.print = function() {
+ return this.executeScript({code: 'window.print();'});
+};
+
+WebViewImpl.prototype.removeContentScripts = function(names) {
+ return WebViewInternal.removeContentScripts(this.viewInstanceId, names);
+};
+
+WebViewImpl.prototype.setUserAgentOverride = function(userAgentOverride) {
+ this.userAgentOverride = userAgentOverride;
+ if (!this.guest.getId()) {
+ // If we are not attached yet, then we will pick up the user agent on
+ // attachment.
+ return false;
+ }
+ WebViewInternal.overrideUserAgent(this.guest.getId(), userAgentOverride);
+ return true;
+};
+
+WebViewImpl.prototype.setZoom = function(zoomFactor, callback) {
+ if (!this.guest.getId()) {
+ this.cachedZoomFactor = zoomFactor;
+ return false;
+ }
+ this.cachedZoomFactor = 1;
+ WebViewInternal.setZoom(this.guest.getId(), zoomFactor, callback);
+ return true;
+};
+
+// -----------------------------------------------------------------------------
+
+WebViewImpl.getApiMethods = function() {
+ return WEB_VIEW_API_METHODS;
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_attributes.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_attributes.js
new file mode 100644
index 00000000000..ce538fdc3df
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_attributes.js
@@ -0,0 +1,277 @@
+// Copyright 2014 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.
+
+// This module implements the attributes of the <webview> tag.
+
+var GuestViewAttributes = require('guestViewAttributes').GuestViewAttributes;
+var WebViewConstants = require('webViewConstants').WebViewConstants;
+var WebViewImpl = require('webView').WebViewImpl;
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+
+// -----------------------------------------------------------------------------
+// AllowScalingAttribute object.
+
+// Attribute that specifies whether scaling is allowed in the webview.
+function AllowScalingAttribute(view) {
+ GuestViewAttributes.BooleanAttribute.call(
+ this, WebViewConstants.ATTRIBUTE_ALLOWSCALING, view);
+}
+
+AllowScalingAttribute.prototype.__proto__ =
+ GuestViewAttributes.BooleanAttribute.prototype;
+
+AllowScalingAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ if (!this.view.guest.getId())
+ return;
+
+ WebViewInternal.setAllowScaling(this.view.guest.getId(), this.getValue());
+};
+
+// -----------------------------------------------------------------------------
+// AllowTransparencyAttribute object.
+
+// Attribute that specifies whether transparency is allowed in the webview.
+function AllowTransparencyAttribute(view) {
+ GuestViewAttributes.BooleanAttribute.call(
+ this, WebViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, view);
+}
+
+AllowTransparencyAttribute.prototype.__proto__ =
+ GuestViewAttributes.BooleanAttribute.prototype;
+
+AllowTransparencyAttribute.prototype.handleMutation = function(oldValue,
+ newValue) {
+ if (!this.view.guest.getId())
+ return;
+
+ WebViewInternal.setAllowTransparency(this.view.guest.getId(),
+ this.getValue());
+};
+
+// -----------------------------------------------------------------------------
+// AutosizeDimensionAttribute object.
+
+// Attribute used to define the demension limits of autosizing.
+function AutosizeDimensionAttribute(name, view) {
+ GuestViewAttributes.IntegerAttribute.call(this, name, view);
+}
+
+AutosizeDimensionAttribute.prototype.__proto__ =
+ GuestViewAttributes.IntegerAttribute.prototype;
+
+AutosizeDimensionAttribute.prototype.handleMutation = function(
+ oldValue, newValue) {
+ if (!this.view.guest.getId())
+ return;
+
+ this.view.guest.setSize({
+ 'enableAutoSize': this.view.attributes[
+ WebViewConstants.ATTRIBUTE_AUTOSIZE].getValue(),
+ 'min': {
+ 'width': this.view.attributes[
+ WebViewConstants.ATTRIBUTE_MINWIDTH].getValue(),
+ 'height': this.view.attributes[
+ WebViewConstants.ATTRIBUTE_MINHEIGHT].getValue()
+ },
+ 'max': {
+ 'width': this.view.attributes[
+ WebViewConstants.ATTRIBUTE_MAXWIDTH].getValue(),
+ 'height': this.view.attributes[
+ WebViewConstants.ATTRIBUTE_MAXHEIGHT].getValue()
+ }
+ });
+ return;
+};
+
+// -----------------------------------------------------------------------------
+// AutosizeAttribute object.
+
+// Attribute that specifies whether the webview should be autosized.
+function AutosizeAttribute(view) {
+ GuestViewAttributes.BooleanAttribute.call(
+ this, WebViewConstants.ATTRIBUTE_AUTOSIZE, view);
+}
+
+AutosizeAttribute.prototype.__proto__ =
+ GuestViewAttributes.BooleanAttribute.prototype;
+
+AutosizeAttribute.prototype.handleMutation =
+ AutosizeDimensionAttribute.prototype.handleMutation;
+
+// -----------------------------------------------------------------------------
+// NameAttribute object.
+
+// Attribute that sets the guest content's window.name object.
+function NameAttribute(view) {
+ GuestViewAttributes.Attribute.call(
+ this, WebViewConstants.ATTRIBUTE_NAME, view);
+}
+
+NameAttribute.prototype.__proto__ = GuestViewAttributes.Attribute.prototype
+
+NameAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ oldValue = oldValue || '';
+ newValue = newValue || '';
+ if (oldValue === newValue || !this.view.guest.getId())
+ return;
+
+ WebViewInternal.setName(this.view.guest.getId(), newValue);
+};
+
+NameAttribute.prototype.setValue = function(value) {
+ value = value || '';
+ if (value === '')
+ this.view.element.removeAttribute(this.name);
+ else
+ this.view.element.setAttribute(this.name, value);
+};
+
+// -----------------------------------------------------------------------------
+// PartitionAttribute object.
+
+// Attribute representing the state of the storage partition.
+function PartitionAttribute(view) {
+ GuestViewAttributes.Attribute.call(
+ this, WebViewConstants.ATTRIBUTE_PARTITION, view);
+ this.validPartitionId = true;
+}
+
+PartitionAttribute.prototype.__proto__ =
+ GuestViewAttributes.Attribute.prototype;
+
+PartitionAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ newValue = newValue || '';
+
+ // The partition cannot change if the webview has already navigated.
+ if (!this.view.attributes[
+ WebViewConstants.ATTRIBUTE_SRC].beforeFirstNavigation) {
+ window.console.error(WebViewConstants.ERROR_MSG_ALREADY_NAVIGATED);
+ this.setValueIgnoreMutation(oldValue);
+ return;
+ }
+ if (newValue == 'persist:') {
+ this.validPartitionId = false;
+ window.console.error(
+ WebViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE);
+ }
+};
+
+PartitionAttribute.prototype.detach = function() {
+ this.validPartitionId = true;
+};
+
+// -----------------------------------------------------------------------------
+// SrcAttribute object.
+
+// Attribute that handles the location and navigation of the webview.
+function SrcAttribute(view) {
+ GuestViewAttributes.Attribute.call(
+ this, WebViewConstants.ATTRIBUTE_SRC, view);
+ this.setupMutationObserver();
+ this.beforeFirstNavigation = true;
+}
+
+SrcAttribute.prototype.__proto__ = GuestViewAttributes.Attribute.prototype;
+
+SrcAttribute.prototype.setValueIgnoreMutation = function(value) {
+ GuestViewAttributes.Attribute.prototype.setValueIgnoreMutation.call(
+ this, value);
+ // takeRecords() is needed to clear queued up src mutations. Without it, it is
+ // possible for this change to get picked up asyncronously by src's mutation
+ // observer |observer|, and then get handled even though we do not want to
+ // handle this mutation.
+ this.observer.takeRecords();
+}
+
+SrcAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ // Once we have navigated, we don't allow clearing the src attribute.
+ // Once <webview> enters a navigated state, it cannot return to a
+ // placeholder state.
+ if (!newValue && oldValue) {
+ // src attribute changes normally initiate a navigation. We suppress
+ // the next src attribute handler call to avoid reloading the page
+ // on every guest-initiated navigation.
+ this.setValueIgnoreMutation(oldValue);
+ return;
+ }
+ this.parse();
+};
+
+SrcAttribute.prototype.attach = function() {
+ this.parse();
+};
+
+SrcAttribute.prototype.detach = function() {
+ this.beforeFirstNavigation = true;
+};
+
+// The purpose of this mutation observer is to catch assignment to the src
+// attribute without any changes to its value. This is useful in the case
+// where the webview guest has crashed and navigating to the same address
+// spawns off a new process.
+SrcAttribute.prototype.setupMutationObserver =
+ function() {
+ this.observer = new MutationObserver(function(mutations) {
+ $Array.forEach(mutations, function(mutation) {
+ var oldValue = mutation.oldValue;
+ var newValue = this.getValue();
+ if (oldValue != newValue) {
+ return;
+ }
+ this.handleMutation(oldValue, newValue);
+ }.bind(this));
+ }.bind(this));
+ var params = {
+ attributes: true,
+ attributeOldValue: true,
+ attributeFilter: [this.name]
+ };
+ this.observer.observe(this.view.element, params);
+};
+
+SrcAttribute.prototype.parse = function() {
+ if (!this.view.elementAttached ||
+ !this.view.attributes[
+ WebViewConstants.ATTRIBUTE_PARTITION].validPartitionId ||
+ !this.getValue()) {
+ return;
+ }
+
+ if (!this.view.guest.getId()) {
+ if (this.beforeFirstNavigation) {
+ this.beforeFirstNavigation = false;
+ this.view.createGuest();
+ }
+ return;
+ }
+
+ WebViewInternal.navigate(this.view.guest.getId(), this.getValue());
+};
+
+// -----------------------------------------------------------------------------
+
+// Sets up all of the webview attributes.
+WebViewImpl.prototype.setupAttributes = function() {
+ this.attributes[WebViewConstants.ATTRIBUTE_ALLOWSCALING] =
+ new AllowScalingAttribute(this);
+ this.attributes[WebViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY] =
+ new AllowTransparencyAttribute(this);
+ this.attributes[WebViewConstants.ATTRIBUTE_AUTOSIZE] =
+ new AutosizeAttribute(this);
+ this.attributes[WebViewConstants.ATTRIBUTE_NAME] =
+ new NameAttribute(this);
+ this.attributes[WebViewConstants.ATTRIBUTE_PARTITION] =
+ new PartitionAttribute(this);
+ this.attributes[WebViewConstants.ATTRIBUTE_SRC] =
+ new SrcAttribute(this);
+
+ var autosizeAttributes = [WebViewConstants.ATTRIBUTE_MAXHEIGHT,
+ WebViewConstants.ATTRIBUTE_MAXWIDTH,
+ WebViewConstants.ATTRIBUTE_MINHEIGHT,
+ WebViewConstants.ATTRIBUTE_MINWIDTH];
+ for (var i = 0; autosizeAttributes[i]; ++i) {
+ this.attributes[autosizeAttributes[i]] =
+ new AutosizeDimensionAttribute(autosizeAttributes[i], this);
+ }
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_constants.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_constants.js
new file mode 100644
index 00000000000..09110e14749
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_constants.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2014 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.
+
+// This module contains constants used in webview.
+
+// Container for the webview constants.
+var WebViewConstants = {
+ // Attributes.
+ ATTRIBUTE_ALLOWTRANSPARENCY: 'allowtransparency',
+ ATTRIBUTE_ALLOWSCALING: 'allowscaling',
+ ATTRIBUTE_AUTOSIZE: 'autosize',
+ ATTRIBUTE_MAXHEIGHT: 'maxheight',
+ ATTRIBUTE_MAXWIDTH: 'maxwidth',
+ ATTRIBUTE_MINHEIGHT: 'minheight',
+ ATTRIBUTE_MINWIDTH: 'minwidth',
+ ATTRIBUTE_NAME: 'name',
+ ATTRIBUTE_PARTITION: 'partition',
+ ATTRIBUTE_SRC: 'src',
+
+ // Error/warning messages.
+ ERROR_MSG_ALREADY_NAVIGATED: '<webview>: ' +
+ 'The object has already navigated, so its partition cannot be changed.',
+ ERROR_MSG_CANNOT_INJECT_SCRIPT: '<webview>: ' +
+ 'Script cannot be injected into content until the page has loaded.',
+ ERROR_MSG_DIALOG_ACTION_ALREADY_TAKEN: '<webview>: ' +
+ 'An action has already been taken for this "dialog" event.',
+ ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN: '<webview>: ' +
+ 'An action has already been taken for this "newwindow" event.',
+ ERROR_MSG_PERMISSION_ACTION_ALREADY_TAKEN: '<webview>: ' +
+ 'Permission has already been decided for this "permissionrequest" event.',
+ ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: '<webview>: ' +
+ 'Invalid partition attribute.',
+ WARNING_MSG_DIALOG_REQUEST_BLOCKED: '<webview>: %1 %2 dialog was blocked.',
+ WARNING_MSG_NEWWINDOW_REQUEST_BLOCKED: '<webview>: A new window was blocked.',
+ WARNING_MSG_PERMISSION_REQUEST_BLOCKED: '<webview>: ' +
+ 'The permission request for "%1" has been denied.'
+};
+
+exports.$set('WebViewConstants', $Object.freeze(WebViewConstants));
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_events.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_events.js
new file mode 100644
index 00000000000..1077684de00
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_events.js
@@ -0,0 +1,301 @@
+// Copyright 2014 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.
+
+// Event management for WebView.
+
+var CreateEvent = require('guestViewEvents').CreateEvent;
+var DeclarativeWebRequestSchema =
+ requireNative('schema_registry').GetSchema('declarativeWebRequest');
+var EventBindings = require('event_bindings');
+var GuestViewEvents = require('guestViewEvents').GuestViewEvents;
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+var IdGenerator = requireNative('id_generator');
+var WebRequestEvent = require('webRequestInternal').WebRequestEvent;
+var WebRequestSchema =
+ requireNative('schema_registry').GetSchema('webRequest');
+var WebViewActionRequests =
+ require('webViewActionRequests').WebViewActionRequests;
+
+var WebRequestMessageEvent = CreateEvent('webViewInternal.onMessage');
+
+function WebViewEvents(webViewImpl) {
+ GuestViewEvents.call(this, webViewImpl);
+
+ this.setupWebRequestEvents();
+ this.view.maybeSetupContextMenus();
+}
+
+WebViewEvents.prototype.__proto__ = GuestViewEvents.prototype;
+
+// A dictionary of <webview> extension events to be listened for. This
+// dictionary augments |GuestViewEvents.EVENTS| in guest_view_events.js. See the
+// documentation there for details.
+WebViewEvents.EVENTS = {
+ 'close': {
+ evt: CreateEvent('webViewInternal.onClose')
+ },
+ 'consolemessage': {
+ evt: CreateEvent('webViewInternal.onConsoleMessage'),
+ fields: ['level', 'message', 'line', 'sourceId']
+ },
+ 'contentload': {
+ evt: CreateEvent('webViewInternal.onContentLoad')
+ },
+ 'dialog': {
+ cancelable: true,
+ evt: CreateEvent('webViewInternal.onDialog'),
+ fields: ['defaultPromptText', 'messageText', 'messageType', 'url'],
+ handler: 'handleDialogEvent'
+ },
+ 'droplink': {
+ evt: CreateEvent('webViewInternal.onDropLink'),
+ fields: ['url']
+ },
+ 'exit': {
+ evt: CreateEvent('webViewInternal.onExit'),
+ fields: ['processId', 'reason']
+ },
+ 'exitfullscreen': {
+ evt: CreateEvent('webViewInternal.onExitFullscreen'),
+ fields: ['url'],
+ handler: 'handleFullscreenExitEvent',
+ internal: true
+ },
+ 'findupdate': {
+ evt: CreateEvent('webViewInternal.onFindReply'),
+ fields: [
+ 'searchText',
+ 'numberOfMatches',
+ 'activeMatchOrdinal',
+ 'selectionRect',
+ 'canceled',
+ 'finalUpdate'
+ ]
+ },
+ 'framenamechanged': {
+ evt: CreateEvent('webViewInternal.onFrameNameChanged'),
+ handler: 'handleFrameNameChangedEvent',
+ internal: true
+ },
+ 'loadabort': {
+ cancelable: true,
+ evt: CreateEvent('webViewInternal.onLoadAbort'),
+ fields: ['url', 'isTopLevel', 'code', 'reason'],
+ handler: 'handleLoadAbortEvent'
+ },
+ 'loadcommit': {
+ evt: CreateEvent('webViewInternal.onLoadCommit'),
+ fields: ['url', 'isTopLevel'],
+ handler: 'handleLoadCommitEvent'
+ },
+ 'loadprogress': {
+ evt: CreateEvent('webViewInternal.onLoadProgress'),
+ fields: ['url', 'progress']
+ },
+ 'loadredirect': {
+ evt: CreateEvent('webViewInternal.onLoadRedirect'),
+ fields: ['isTopLevel', 'oldUrl', 'newUrl']
+ },
+ 'loadstart': {
+ evt: CreateEvent('webViewInternal.onLoadStart'),
+ fields: ['url', 'isTopLevel']
+ },
+ 'loadstop': {
+ evt: CreateEvent('webViewInternal.onLoadStop')
+ },
+ 'newwindow': {
+ cancelable: true,
+ evt: CreateEvent('webViewInternal.onNewWindow'),
+ fields: [
+ 'initialHeight',
+ 'initialWidth',
+ 'targetUrl',
+ 'windowOpenDisposition',
+ 'name'
+ ],
+ handler: 'handleNewWindowEvent'
+ },
+ 'permissionrequest': {
+ cancelable: true,
+ evt: CreateEvent('webViewInternal.onPermissionRequest'),
+ fields: [
+ 'identifier',
+ 'lastUnlockedBySelf',
+ 'name',
+ 'permission',
+ 'requestMethod',
+ 'url',
+ 'userGesture'
+ ],
+ handler: 'handlePermissionEvent'
+ },
+ 'responsive': {
+ evt: CreateEvent('webViewInternal.onResponsive'),
+ fields: ['processId']
+ },
+ 'sizechanged': {
+ evt: CreateEvent('webViewInternal.onSizeChanged'),
+ fields: ['oldHeight', 'oldWidth', 'newHeight', 'newWidth'],
+ handler: 'handleSizeChangedEvent'
+ },
+ 'unresponsive': {
+ evt: CreateEvent('webViewInternal.onUnresponsive'),
+ fields: ['processId']
+ },
+ 'zoomchange': {
+ evt: CreateEvent('webViewInternal.onZoomChange'),
+ fields: ['oldZoomFactor', 'newZoomFactor']
+ }
+};
+
+WebViewEvents.prototype.setupWebRequestEvents = function() {
+ var request = {};
+ var createWebRequestEvent = function(webRequestEvent) {
+ return this.weakWrapper(function() {
+ if (!this[webRequestEvent.name]) {
+ this[webRequestEvent.name] =
+ new WebRequestEvent(
+ 'webViewInternal.' + webRequestEvent.name,
+ webRequestEvent.parameters,
+ webRequestEvent.extraParameters, webRequestEvent.options,
+ this.view.viewInstanceId);
+ }
+ return this[webRequestEvent.name];
+ });
+ }.bind(this);
+
+ var createDeclarativeWebRequestEvent = function(webRequestEvent) {
+ return this.weakWrapper(function() {
+ if (!this[webRequestEvent.name]) {
+ // The onMessage event gets a special event type because we want
+ // the listener to fire only for messages targeted for this particular
+ // <webview>.
+ var EventClass = webRequestEvent.name === 'onMessage' ?
+ DeclarativeWebRequestEvent : EventBindings.Event;
+ this[webRequestEvent.name] =
+ new EventClass(
+ 'webViewInternal.declarativeWebRequest.' + webRequestEvent.name,
+ webRequestEvent.parameters,
+ webRequestEvent.options,
+ this.view.viewInstanceId);
+ }
+ return this[webRequestEvent.name];
+ });
+ }.bind(this);
+
+ for (var i = 0; i < DeclarativeWebRequestSchema.events.length; ++i) {
+ var eventSchema = DeclarativeWebRequestSchema.events[i];
+ var webRequestEvent = createDeclarativeWebRequestEvent(eventSchema);
+ Object.defineProperty(
+ request,
+ eventSchema.name,
+ {
+ get: webRequestEvent,
+ enumerable: true
+ }
+ );
+ }
+
+ // Populate the WebRequest events from the API definition.
+ for (var i = 0; i < WebRequestSchema.events.length; ++i) {
+ var webRequestEvent = createWebRequestEvent(WebRequestSchema.events[i]);
+ Object.defineProperty(
+ request,
+ WebRequestSchema.events[i].name,
+ {
+ get: webRequestEvent,
+ enumerable: true
+ }
+ );
+ }
+
+ this.view.setRequestPropertyOnWebViewElement(request);
+};
+
+WebViewEvents.prototype.getEvents = function() {
+ return WebViewEvents.EVENTS;
+};
+
+WebViewEvents.prototype.handleDialogEvent = function(event, eventName) {
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ new WebViewActionRequests.Dialog(this.view, event, webViewEvent);
+};
+
+WebViewEvents.prototype.handleFrameNameChangedEvent = function(event) {
+ this.view.onFrameNameChanged(event.name);
+};
+
+WebViewEvents.prototype.handleFullscreenExitEvent = function(event, eventName) {
+ document.webkitCancelFullScreen();
+};
+
+WebViewEvents.prototype.handleLoadAbortEvent = function(event, eventName) {
+ var showWarningMessage = function(code, reason) {
+ var WARNING_MSG_LOAD_ABORTED = '<webview>: ' +
+ 'The load has aborted with error %1: %2.';
+ window.console.warn(
+ WARNING_MSG_LOAD_ABORTED.replace('%1', code).replace('%2', reason));
+ };
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ if (this.view.dispatchEvent(webViewEvent)) {
+ showWarningMessage(event.code, event.reason);
+ }
+};
+
+WebViewEvents.prototype.handleLoadCommitEvent = function(event, eventName) {
+ this.view.onLoadCommit(event.baseUrlForDataUrl,
+ event.currentEntryIndex,
+ event.entryCount,
+ event.processId,
+ event.url,
+ event.isTopLevel);
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ this.view.dispatchEvent(webViewEvent);
+};
+
+WebViewEvents.prototype.handleNewWindowEvent = function(event, eventName) {
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ new WebViewActionRequests.NewWindow(this.view, event, webViewEvent);
+};
+
+WebViewEvents.prototype.handlePermissionEvent = function(event, eventName) {
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ if (event.permission === 'fullscreen') {
+ new WebViewActionRequests.FullscreenPermissionRequest(
+ this.view, event, webViewEvent);
+ } else {
+ new WebViewActionRequests.PermissionRequest(this.view, event, webViewEvent);
+ }
+};
+
+WebViewEvents.prototype.handleSizeChangedEvent = function(event, eventName) {
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ this.view.onSizeChanged(webViewEvent);
+};
+
+function DeclarativeWebRequestEvent(opt_eventName,
+ opt_argSchemas,
+ opt_eventOptions,
+ opt_webViewInstanceId) {
+ var subEventName = opt_eventName + '/' + IdGenerator.GetNextId();
+ EventBindings.Event.call(this,
+ subEventName,
+ opt_argSchemas,
+ opt_eventOptions,
+ opt_webViewInstanceId);
+
+ var view = GuestViewInternalNatives.GetViewFromID(opt_webViewInstanceId || 0);
+ if (!view) {
+ return;
+ }
+ view.events.addScopedListener(WebRequestMessageEvent, function() {
+ // Re-dispatch to subEvent's listeners.
+ $Function.apply(this.dispatch, this, $Array.slice(arguments));
+ }.bind(this), {instanceId: opt_webViewInstanceId || 0});
+}
+
+DeclarativeWebRequestEvent.prototype.__proto__ = EventBindings.Event.prototype;
+
+// Exports.
+exports.$set('WebViewEvents', WebViewEvents);
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js
new file mode 100644
index 00000000000..96331df2a89
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js
@@ -0,0 +1,24 @@
+// Copyright 2015 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.
+
+// This module implements experimental API for <webview>.
+// See web_view.js and web_view_api_methods.js for details.
+//
+// <webview> Experimental API is only available on canary and channels of
+// Chrome.
+
+var WebViewImpl = require('webView').WebViewImpl;
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+
+// An array of <webview>'s experimental API methods. See |WEB_VIEW_API_METHODS|
+// in web_view_api_methods.js for more details.
+var WEB_VIEW_EXPERIMENTAL_API_METHODS = [
+ // Captures the visible region of the WebView contents into a bitmap.
+ 'captureVisibleRegion'
+];
+
+// Registers the experimantal WebVIew API when available.
+WebViewImpl.maybeGetExperimentalApiMethods = function() {
+ return WEB_VIEW_EXPERIMENTAL_API_METHODS;
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_internal.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_internal.js
new file mode 100644
index 00000000000..4d2c36c2dad
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_internal.js
@@ -0,0 +1,7 @@
+// Copyright 2014 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.
+
+exports.$set(
+ 'WebViewInternal',
+ require('binding').Binding.create('webViewInternal').generate());
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js
new file mode 100644
index 00000000000..61b553b693f
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js
@@ -0,0 +1,55 @@
+// Copyright 2014 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.
+
+// Custom binding for the webViewRequest API.
+
+var binding = require('binding').Binding.create('webViewRequest');
+
+var declarativeWebRequestSchema =
+ requireNative('schema_registry').GetSchema('declarativeWebRequest');
+var utils = require('utils');
+var validate = require('schemaUtils').validate;
+
+binding.registerCustomHook(function(api) {
+ var webViewRequest = api.compiledApi;
+
+ // Returns the schema definition of type |typeId| defined in
+ // |declarativeWebRequestScheme.types|.
+ function getSchema(typeId) {
+ return utils.lookup(declarativeWebRequestSchema.types,
+ 'id',
+ 'declarativeWebRequest.' + typeId);
+ }
+
+ // Helper function for the constructor of concrete datatypes of the
+ // declarative webRequest API.
+ // Makes sure that |this| contains the union of parameters and
+ // {'instanceType': 'declarativeWebRequest.' + typeId} and validates the
+ // generated union dictionary against the schema for |typeId|.
+ function setupInstance(instance, parameters, typeId) {
+ for (var key in parameters) {
+ if ($Object.hasOwnProperty(parameters, key)) {
+ instance[key] = parameters[key];
+ }
+ }
+
+ instance.instanceType = 'declarativeWebRequest.' + typeId;
+ var schema = getSchema(typeId);
+ validate([instance], [schema]);
+ }
+
+ // Setup all data types for the declarative webRequest API from the schema.
+ for (var i = 0; i < declarativeWebRequestSchema.types.length; ++i) {
+ var typeSchema = declarativeWebRequestSchema.types[i];
+ var typeId = typeSchema.id.replace('declarativeWebRequest.', '');
+ var action = function(typeId) {
+ return function(parameters) {
+ setupInstance(this, parameters, typeId);
+ };
+ }(typeId);
+ webViewRequest[typeId] = action;
+ }
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/i18n_custom_bindings.js b/chromium/extensions/renderer/resources/i18n_custom_bindings.js
new file mode 100644
index 00000000000..de69fa2ebe0
--- /dev/null
+++ b/chromium/extensions/renderer/resources/i18n_custom_bindings.js
@@ -0,0 +1,49 @@
+// Copyright 2014 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.
+
+// Custom binding for the i18n API.
+
+var binding = require('binding').Binding.create('i18n');
+
+var i18nNatives = requireNative('i18n');
+var GetL10nMessage = i18nNatives.GetL10nMessage;
+var GetL10nUILanguage = i18nNatives.GetL10nUILanguage;
+var DetectTextLanguage = i18nNatives.DetectTextLanguage;
+
+binding.registerCustomHook(function(bindingsAPI, extensionId) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+
+ apiFunctions.setUpdateArgumentsPreValidate('getMessage', function() {
+ var args = $Array.slice(arguments);
+
+ // The first argument is the message, and should be a string.
+ var message = args[0];
+ if (typeof(message) !== 'string') {
+ console.warn(extensionId + ': the first argument to getMessage should ' +
+ 'be type "string", was ' + message +
+ ' (type "' + typeof(message) + '")');
+ args[0] = String(message);
+ }
+
+ return args;
+ });
+
+ apiFunctions.setHandleRequest('getMessage',
+ function(messageName, substitutions) {
+ return GetL10nMessage(messageName, substitutions, extensionId);
+ });
+
+ apiFunctions.setHandleRequest('getUILanguage', function() {
+ return GetL10nUILanguage();
+ });
+
+ apiFunctions.setHandleRequest('detectLanguage', function(text, callback) {
+ window.setTimeout(function() {
+ var response = DetectTextLanguage(text);
+ callback(response);
+ }, 0);
+ });
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/image_util.js b/chromium/extensions/renderer/resources/image_util.js
new file mode 100644
index 00000000000..a7a27a64a66
--- /dev/null
+++ b/chromium/extensions/renderer/resources/image_util.js
@@ -0,0 +1,82 @@
+// Copyright 2014 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.
+
+// This function takes an object |imageSpec| with the key |path| -
+// corresponding to the internet URL to be translated - and optionally
+// |width| and |height| which are the maximum dimensions to be used when
+// converting the image.
+function loadImageData(imageSpec, callbacks) {
+ var path = imageSpec.path;
+ var img = new Image();
+ if (typeof callbacks.onerror === 'function') {
+ img.onerror = function() {
+ callbacks.onerror({ problem: 'could_not_load', path: path });
+ };
+ }
+ img.onload = function() {
+ var canvas = document.createElement('canvas');
+
+ if (img.width <= 0 || img.height <= 0) {
+ callbacks.onerror({ problem: 'image_size_invalid', path: path});
+ return;
+ }
+
+ var scaleFactor = 1;
+ if (imageSpec.width && imageSpec.width < img.width)
+ scaleFactor = imageSpec.width / img.width;
+
+ if (imageSpec.height && imageSpec.height < img.height) {
+ var heightScale = imageSpec.height / img.height;
+ if (heightScale < scaleFactor)
+ scaleFactor = heightScale;
+ }
+
+ canvas.width = img.width * scaleFactor;
+ canvas.height = img.height * scaleFactor;
+
+ var canvas_context = canvas.getContext('2d');
+ canvas_context.clearRect(0, 0, canvas.width, canvas.height);
+ canvas_context.drawImage(img, 0, 0, canvas.width, canvas.height);
+ try {
+ var imageData = canvas_context.getImageData(
+ 0, 0, canvas.width, canvas.height);
+ if (typeof callbacks.oncomplete === 'function') {
+ callbacks.oncomplete(
+ imageData.width, imageData.height, imageData.data.buffer);
+ }
+ } catch (e) {
+ if (typeof callbacks.onerror === 'function') {
+ callbacks.onerror({ problem: 'data_url_unavailable', path: path });
+ }
+ }
+ }
+ img.src = path;
+}
+
+function on_complete_index(index, err, loading, finished, callbacks) {
+ return function(width, height, imageData) {
+ delete loading[index];
+ finished[index] = { width: width, height: height, data: imageData };
+ if (err)
+ callbacks.onerror(index);
+ if ($Object.keys(loading).length == 0)
+ callbacks.oncomplete(finished);
+ }
+}
+
+function loadAllImages(imageSpecs, callbacks) {
+ var loading = {}, finished = [],
+ index, pathname;
+
+ for (var index = 0; index < imageSpecs.length; index++) {
+ loading[index] = imageSpecs[index];
+ loadImageData(imageSpecs[index], {
+ oncomplete: on_complete_index(index, false, loading, finished, callbacks),
+ onerror: on_complete_index(index, true, loading, finished, callbacks)
+ });
+ }
+}
+
+exports.$set('loadImageData', loadImageData);
+exports.$set('loadAllImages', loadAllImages);
diff --git a/chromium/extensions/renderer/resources/json_schema.js b/chromium/extensions/renderer/resources/json_schema.js
new file mode 100644
index 00000000000..447d1ea21c2
--- /dev/null
+++ b/chromium/extensions/renderer/resources/json_schema.js
@@ -0,0 +1,525 @@
+// Copyright 2014 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.
+
+// -----------------------------------------------------------------------------
+// NOTE: If you change this file you need to touch
+// extension_renderer_resources.grd to have your change take effect.
+// -----------------------------------------------------------------------------
+
+//==============================================================================
+// This file contains a class that implements a subset of JSON Schema.
+// See: http://www.json.com/json-schema-proposal/ for more details.
+//
+// The following features of JSON Schema are not implemented:
+// - requires
+// - unique
+// - disallow
+// - union types (but replaced with 'choices')
+//
+// The following properties are not applicable to the interface exposed by
+// this class:
+// - options
+// - readonly
+// - title
+// - description
+// - format
+// - default
+// - transient
+// - hidden
+//
+// There are also these departures from the JSON Schema proposal:
+// - function and undefined types are supported
+// - null counts as 'unspecified' for optional values
+// - added the 'choices' property, to allow specifying a list of possible types
+// for a value
+// - by default an "object" typed schema does not allow additional properties.
+// if present, "additionalProperties" is to be a schema against which all
+// additional properties will be validated.
+//==============================================================================
+
+var loadTypeSchema = require('utils').loadTypeSchema;
+var CHECK = requireNative('logging').CHECK;
+
+function isInstanceOfClass(instance, className) {
+ while ((instance = instance.__proto__)) {
+ if (instance.constructor.name == className)
+ return true;
+ }
+ return false;
+}
+
+function isOptionalValue(value) {
+ return typeof(value) === 'undefined' || value === null;
+}
+
+function enumToString(enumValue) {
+ if (enumValue.name === undefined)
+ return enumValue;
+
+ return enumValue.name;
+}
+
+/**
+ * Validates an instance against a schema and accumulates errors. Usage:
+ *
+ * var validator = new JSONSchemaValidator();
+ * validator.validate(inst, schema);
+ * if (validator.errors.length == 0)
+ * console.log("Valid!");
+ * else
+ * console.log(validator.errors);
+ *
+ * The errors property contains a list of objects. Each object has two
+ * properties: "path" and "message". The "path" property contains the path to
+ * the key that had the problem, and the "message" property contains a sentence
+ * describing the error.
+ */
+function JSONSchemaValidator() {
+ this.errors = [];
+ this.types = [];
+}
+
+JSONSchemaValidator.messages = {
+ invalidEnum: "Value must be one of: [*].",
+ propertyRequired: "Property is required.",
+ unexpectedProperty: "Unexpected property.",
+ arrayMinItems: "Array must have at least * items.",
+ arrayMaxItems: "Array must not have more than * items.",
+ itemRequired: "Item is required.",
+ stringMinLength: "String must be at least * characters long.",
+ stringMaxLength: "String must not be more than * characters long.",
+ stringPattern: "String must match the pattern: *.",
+ numberFiniteNotNan: "Value must not be *.",
+ numberMinValue: "Value must not be less than *.",
+ numberMaxValue: "Value must not be greater than *.",
+ numberIntValue: "Value must fit in a 32-bit signed integer.",
+ numberMaxDecimal: "Value must not have more than * decimal places.",
+ invalidType: "Expected '*' but got '*'.",
+ invalidTypeIntegerNumber:
+ "Expected 'integer' but got 'number', consider using Math.round().",
+ invalidChoice: "Value does not match any valid type choices.",
+ invalidPropertyType: "Missing property type.",
+ schemaRequired: "Schema value required.",
+ unknownSchemaReference: "Unknown schema reference: *.",
+ notInstance: "Object must be an instance of *."
+};
+
+/**
+ * Builds an error message. Key is the property in the |errors| object, and
+ * |opt_replacements| is an array of values to replace "*" characters with.
+ */
+JSONSchemaValidator.formatError = function(key, opt_replacements) {
+ var message = this.messages[key];
+ if (opt_replacements) {
+ for (var i = 0; i < opt_replacements.length; i++) {
+ message = message.replace("*", opt_replacements[i]);
+ }
+ }
+ return message;
+};
+
+/**
+ * Classifies a value as one of the JSON schema primitive types. Note that we
+ * don't explicitly disallow 'function', because we want to allow functions in
+ * the input values.
+ */
+JSONSchemaValidator.getType = function(value) {
+ var s = typeof value;
+
+ if (s == "object") {
+ if (value === null) {
+ return "null";
+ } else if (Object.prototype.toString.call(value) == "[object Array]") {
+ return "array";
+ } else if (Object.prototype.toString.call(value) ==
+ "[object ArrayBuffer]") {
+ return "binary";
+ }
+ } else if (s == "number") {
+ if (value % 1 == 0) {
+ return "integer";
+ }
+ }
+
+ return s;
+};
+
+/**
+ * Add types that may be referenced by validated schemas that reference them
+ * with "$ref": <typeId>. Each type must be a valid schema and define an
+ * "id" property.
+ */
+JSONSchemaValidator.prototype.addTypes = function(typeOrTypeList) {
+ function addType(validator, type) {
+ if (!type.id)
+ throw new Error("Attempt to addType with missing 'id' property");
+ validator.types[type.id] = type;
+ }
+
+ if (typeOrTypeList instanceof Array) {
+ for (var i = 0; i < typeOrTypeList.length; i++) {
+ addType(this, typeOrTypeList[i]);
+ }
+ } else {
+ addType(this, typeOrTypeList);
+ }
+}
+
+/**
+ * Returns a list of strings of the types that this schema accepts.
+ */
+JSONSchemaValidator.prototype.getAllTypesForSchema = function(schema) {
+ var schemaTypes = [];
+ if (schema.type)
+ $Array.push(schemaTypes, schema.type);
+ if (schema.choices) {
+ for (var i = 0; i < schema.choices.length; i++) {
+ var choiceTypes = this.getAllTypesForSchema(schema.choices[i]);
+ schemaTypes = $Array.concat(schemaTypes, choiceTypes);
+ }
+ }
+ var ref = schema['$ref'];
+ if (ref) {
+ var type = this.getOrAddType(ref);
+ CHECK(type, 'Could not find type ' + ref);
+ schemaTypes = $Array.concat(schemaTypes, this.getAllTypesForSchema(type));
+ }
+ return schemaTypes;
+};
+
+JSONSchemaValidator.prototype.getOrAddType = function(typeName) {
+ if (!this.types[typeName])
+ this.types[typeName] = loadTypeSchema(typeName);
+ return this.types[typeName];
+};
+
+/**
+ * Returns true if |schema| would accept an argument of type |type|.
+ */
+JSONSchemaValidator.prototype.isValidSchemaType = function(type, schema) {
+ if (type == 'any')
+ return true;
+
+ // TODO(kalman): I don't understand this code. How can type be "null"?
+ if (schema.optional && (type == "null" || type == "undefined"))
+ return true;
+
+ var schemaTypes = this.getAllTypesForSchema(schema);
+ for (var i = 0; i < schemaTypes.length; i++) {
+ if (schemaTypes[i] == "any" || type == schemaTypes[i] ||
+ (type == "integer" && schemaTypes[i] == "number"))
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Returns true if there is a non-null argument that both |schema1| and
+ * |schema2| would accept.
+ */
+JSONSchemaValidator.prototype.checkSchemaOverlap = function(schema1, schema2) {
+ var schema1Types = this.getAllTypesForSchema(schema1);
+ for (var i = 0; i < schema1Types.length; i++) {
+ if (this.isValidSchemaType(schema1Types[i], schema2))
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Validates an instance against a schema. The instance can be any JavaScript
+ * value and will be validated recursively. When this method returns, the
+ * |errors| property will contain a list of errors, if any.
+ */
+JSONSchemaValidator.prototype.validate = function(instance, schema, opt_path) {
+ var path = opt_path || "";
+
+ if (!schema) {
+ this.addError(path, "schemaRequired");
+ return;
+ }
+
+ // If this schema defines itself as reference type, save it in this.types.
+ if (schema.id)
+ this.types[schema.id] = schema;
+
+ // If the schema has an extends property, the instance must validate against
+ // that schema too.
+ if (schema.extends)
+ this.validate(instance, schema.extends, path);
+
+ // If the schema has a $ref property, the instance must validate against
+ // that schema too. It must be present in this.types to be referenced.
+ var ref = schema["$ref"];
+ if (ref) {
+ if (!this.getOrAddType(ref))
+ this.addError(path, "unknownSchemaReference", [ ref ]);
+ else
+ this.validate(instance, this.getOrAddType(ref), path)
+ }
+
+ // If the schema has a choices property, the instance must validate against at
+ // least one of the items in that array.
+ if (schema.choices) {
+ this.validateChoices(instance, schema, path);
+ return;
+ }
+
+ // If the schema has an enum property, the instance must be one of those
+ // values.
+ if (schema.enum) {
+ if (!this.validateEnum(instance, schema, path))
+ return;
+ }
+
+ if (schema.type && schema.type != "any") {
+ if (!this.validateType(instance, schema, path))
+ return;
+
+ // Type-specific validation.
+ switch (schema.type) {
+ case "object":
+ this.validateObject(instance, schema, path);
+ break;
+ case "array":
+ this.validateArray(instance, schema, path);
+ break;
+ case "string":
+ this.validateString(instance, schema, path);
+ break;
+ case "number":
+ case "integer":
+ this.validateNumber(instance, schema, path);
+ break;
+ }
+ }
+};
+
+/**
+ * Validates an instance against a choices schema. The instance must match at
+ * least one of the provided choices.
+ */
+JSONSchemaValidator.prototype.validateChoices =
+ function(instance, schema, path) {
+ var originalErrors = this.errors;
+
+ for (var i = 0; i < schema.choices.length; i++) {
+ this.errors = [];
+ this.validate(instance, schema.choices[i], path);
+ if (this.errors.length == 0) {
+ this.errors = originalErrors;
+ return;
+ }
+ }
+
+ this.errors = originalErrors;
+ this.addError(path, "invalidChoice");
+};
+
+/**
+ * Validates an instance against a schema with an enum type. Populates the
+ * |errors| property, and returns a boolean indicating whether the instance
+ * validates.
+ */
+JSONSchemaValidator.prototype.validateEnum = function(instance, schema, path) {
+ for (var i = 0; i < schema.enum.length; i++) {
+ if (instance === enumToString(schema.enum[i]))
+ return true;
+ }
+
+ this.addError(path, "invalidEnum",
+ [$Array.join($Array.map(schema.enum, enumToString), ", ")]);
+ return false;
+};
+
+/**
+ * Validates an instance against an object schema and populates the errors
+ * property.
+ */
+JSONSchemaValidator.prototype.validateObject =
+ function(instance, schema, path) {
+ if (schema.properties) {
+ for (var prop in schema.properties) {
+ // It is common in JavaScript to add properties to Object.prototype. This
+ // check prevents such additions from being interpreted as required
+ // schema properties.
+ // TODO(aa): If it ever turns out that we actually want this to work,
+ // there are other checks we could put here, like requiring that schema
+ // properties be objects that have a 'type' property.
+ if (!$Object.hasOwnProperty(schema.properties, prop))
+ continue;
+
+ var propPath = path ? path + "." + prop : prop;
+ if (schema.properties[prop] == undefined) {
+ this.addError(propPath, "invalidPropertyType");
+ } else if (prop in instance && !isOptionalValue(instance[prop])) {
+ this.validate(instance[prop], schema.properties[prop], propPath);
+ } else if (!schema.properties[prop].optional) {
+ this.addError(propPath, "propertyRequired");
+ }
+ }
+ }
+
+ // If "instanceof" property is set, check that this object inherits from
+ // the specified constructor (function).
+ if (schema.isInstanceOf) {
+ if (!isInstanceOfClass(instance, schema.isInstanceOf))
+ this.addError(propPath, "notInstance", [schema.isInstanceOf]);
+ }
+
+ // Exit early from additional property check if "type":"any" is defined.
+ if (schema.additionalProperties &&
+ schema.additionalProperties.type &&
+ schema.additionalProperties.type == "any") {
+ return;
+ }
+
+ // By default, additional properties are not allowed on instance objects. This
+ // can be overridden by setting the additionalProperties property to a schema
+ // which any additional properties must validate against.
+ for (var prop in instance) {
+ if (schema.properties && prop in schema.properties)
+ continue;
+
+ // Any properties inherited through the prototype are ignored.
+ if (!$Object.hasOwnProperty(instance, prop))
+ continue;
+
+ var propPath = path ? path + "." + prop : prop;
+ if (schema.additionalProperties)
+ this.validate(instance[prop], schema.additionalProperties, propPath);
+ else
+ this.addError(propPath, "unexpectedProperty");
+ }
+};
+
+/**
+ * Validates an instance against an array schema and populates the errors
+ * property.
+ */
+JSONSchemaValidator.prototype.validateArray = function(instance, schema, path) {
+ var typeOfItems = JSONSchemaValidator.getType(schema.items);
+
+ if (typeOfItems == 'object') {
+ if (schema.minItems && instance.length < schema.minItems) {
+ this.addError(path, "arrayMinItems", [schema.minItems]);
+ }
+
+ if (typeof schema.maxItems != "undefined" &&
+ instance.length > schema.maxItems) {
+ this.addError(path, "arrayMaxItems", [schema.maxItems]);
+ }
+
+ // If the items property is a single schema, each item in the array must
+ // have that schema.
+ for (var i = 0; i < instance.length; i++) {
+ this.validate(instance[i], schema.items, path + "." + i);
+ }
+ } else if (typeOfItems == 'array') {
+ // If the items property is an array of schemas, each item in the array must
+ // validate against the corresponding schema.
+ for (var i = 0; i < schema.items.length; i++) {
+ var itemPath = path ? path + "." + i : String(i);
+ if (i in instance && !isOptionalValue(instance[i])) {
+ this.validate(instance[i], schema.items[i], itemPath);
+ } else if (!schema.items[i].optional) {
+ this.addError(itemPath, "itemRequired");
+ }
+ }
+
+ if (schema.additionalProperties) {
+ for (var i = schema.items.length; i < instance.length; i++) {
+ var itemPath = path ? path + "." + i : String(i);
+ this.validate(instance[i], schema.additionalProperties, itemPath);
+ }
+ } else {
+ if (instance.length > schema.items.length) {
+ this.addError(path, "arrayMaxItems", [schema.items.length]);
+ }
+ }
+ }
+};
+
+/**
+ * Validates a string and populates the errors property.
+ */
+JSONSchemaValidator.prototype.validateString =
+ function(instance, schema, path) {
+ if (schema.minLength && instance.length < schema.minLength)
+ this.addError(path, "stringMinLength", [schema.minLength]);
+
+ if (schema.maxLength && instance.length > schema.maxLength)
+ this.addError(path, "stringMaxLength", [schema.maxLength]);
+
+ if (schema.pattern && !schema.pattern.test(instance))
+ this.addError(path, "stringPattern", [schema.pattern]);
+};
+
+/**
+ * Validates a number and populates the errors property. The instance is
+ * assumed to be a number.
+ */
+JSONSchemaValidator.prototype.validateNumber =
+ function(instance, schema, path) {
+ // Forbid NaN, +Infinity, and -Infinity. Our APIs don't use them, and
+ // JSON serialization encodes them as 'null'. Re-evaluate supporting
+ // them if we add an API that could reasonably take them as a parameter.
+ if (isNaN(instance) ||
+ instance == Number.POSITIVE_INFINITY ||
+ instance == Number.NEGATIVE_INFINITY )
+ this.addError(path, "numberFiniteNotNan", [instance]);
+
+ if (schema.minimum !== undefined && instance < schema.minimum)
+ this.addError(path, "numberMinValue", [schema.minimum]);
+
+ if (schema.maximum !== undefined && instance > schema.maximum)
+ this.addError(path, "numberMaxValue", [schema.maximum]);
+
+ // Check for integer values outside of -2^31..2^31-1.
+ if (schema.type === "integer" && (instance | 0) !== instance)
+ this.addError(path, "numberIntValue", []);
+
+ if (schema.maxDecimal && instance * Math.pow(10, schema.maxDecimal) % 1)
+ this.addError(path, "numberMaxDecimal", [schema.maxDecimal]);
+};
+
+/**
+ * Validates the primitive type of an instance and populates the errors
+ * property. Returns true if the instance validates, false otherwise.
+ */
+JSONSchemaValidator.prototype.validateType = function(instance, schema, path) {
+ var actualType = JSONSchemaValidator.getType(instance);
+ if (schema.type == actualType ||
+ (schema.type == "number" && actualType == "integer")) {
+ return true;
+ } else if (schema.type == "integer" && actualType == "number") {
+ this.addError(path, "invalidTypeIntegerNumber");
+ return false;
+ } else {
+ this.addError(path, "invalidType", [schema.type, actualType]);
+ return false;
+ }
+};
+
+/**
+ * Adds an error message. |key| is an index into the |messages| object.
+ * |replacements| is an array of values to replace '*' characters in the
+ * message.
+ */
+JSONSchemaValidator.prototype.addError = function(path, key, replacements) {
+ $Array.push(this.errors, {
+ path: path,
+ message: JSONSchemaValidator.formatError(key, replacements)
+ });
+};
+
+/**
+ * Resets errors to an empty list so you can call 'validate' again.
+ */
+JSONSchemaValidator.prototype.resetErrors = function() {
+ this.errors = [];
+};
+
+exports.$set('JSONSchemaValidator', JSONSchemaValidator);
diff --git a/chromium/extensions/renderer/resources/keep_alive.js b/chromium/extensions/renderer/resources/keep_alive.js
new file mode 100644
index 00000000000..5269c30285f
--- /dev/null
+++ b/chromium/extensions/renderer/resources/keep_alive.js
@@ -0,0 +1,41 @@
+// Copyright 2014 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.
+
+define('keep_alive', [
+ 'content/public/renderer/frame_service_registry',
+ 'extensions/common/mojo/keep_alive.mojom',
+ 'mojo/public/js/core',
+], function(serviceProvider, mojom, core) {
+
+ /**
+ * An object that keeps the background page alive until closed.
+ * @constructor
+ * @alias module:keep_alive~KeepAlive
+ */
+ function KeepAlive() {
+ /**
+ * The handle to the keep-alive object in the browser.
+ * @type {!MojoHandle}
+ * @private
+ */
+ this.handle_ = serviceProvider.connectToService(mojom.KeepAlive.name);
+ }
+
+ /**
+ * Removes this keep-alive.
+ */
+ KeepAlive.prototype.close = function() {
+ core.close(this.handle_);
+ };
+
+ var exports = {};
+
+ return {
+ /**
+ * Creates a keep-alive.
+ * @return {!module:keep_alive~KeepAlive} A new keep-alive.
+ */
+ createKeepAlive: function() { return new KeepAlive(); }
+ };
+});
diff --git a/chromium/extensions/renderer/resources/last_error.js b/chromium/extensions/renderer/resources/last_error.js
new file mode 100644
index 00000000000..fc0c7b5e4d3
--- /dev/null
+++ b/chromium/extensions/renderer/resources/last_error.js
@@ -0,0 +1,143 @@
+// Copyright 2014 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.
+
+var GetAvailability = requireNative('v8_context').GetAvailability;
+var GetGlobal = requireNative('sendRequest').GetGlobal;
+
+// Utility for setting chrome.*.lastError.
+//
+// A utility here is useful for two reasons:
+// 1. For backwards compatibility we need to set chrome.extension.lastError,
+// but not all contexts actually have access to the extension namespace.
+// 2. When calling across contexts, the global object that gets lastError set
+// needs to be that of the caller. We force callers to explicitly specify
+// the chrome object to try to prevent bugs here.
+
+/**
+ * Sets the last error for |name| on |targetChrome| to |message| with an
+ * optional |stack|.
+ */
+function set(name, message, stack, targetChrome) {
+ if (!targetChrome) {
+ var errorMessage = name + ': ' + message;
+ if (stack != null && stack != '')
+ errorMessage += '\n' + stack;
+ throw new Error('No chrome object to set error: ' + errorMessage);
+ }
+ clear(targetChrome); // in case somebody has set a sneaky getter/setter
+
+ var errorObject = { message: message };
+ if (GetAvailability('extension.lastError').is_available)
+ targetChrome.extension.lastError = errorObject;
+
+ assertRuntimeIsAvailable();
+
+ // We check to see if developers access runtime.lastError in order to decide
+ // whether or not to log it in the (error) console.
+ privates(targetChrome.runtime).accessedLastError = false;
+ $Object.defineProperty(targetChrome.runtime, 'lastError', {
+ configurable: true,
+ get: function() {
+ privates(targetChrome.runtime).accessedLastError = true;
+ return errorObject;
+ },
+ set: function(error) {
+ errorObject = errorObject;
+ }});
+};
+
+/**
+ * Check if anyone has checked chrome.runtime.lastError since it was set.
+ * @param {Object} targetChrome the Chrome object to check.
+ * @return boolean True if the lastError property was set.
+ */
+function hasAccessed(targetChrome) {
+ assertRuntimeIsAvailable();
+ return privates(targetChrome.runtime).accessedLastError === true;
+}
+
+/**
+ * Check whether there is an error set on |targetChrome| without setting
+ * |accessedLastError|.
+ * @param {Object} targetChrome the Chrome object to check.
+ * @return boolean Whether lastError has been set.
+ */
+function hasError(targetChrome) {
+ if (!targetChrome)
+ throw new Error('No target chrome to check');
+
+ assertRuntimeIsAvailable();
+ if ('lastError' in targetChrome.runtime)
+ return true;
+
+ return false;
+};
+
+/**
+ * Clears the last error on |targetChrome|.
+ */
+function clear(targetChrome) {
+ if (!targetChrome)
+ throw new Error('No target chrome to clear error');
+
+ if (GetAvailability('extension.lastError').is_available)
+ delete targetChrome.extension.lastError;
+
+ assertRuntimeIsAvailable();
+ delete targetChrome.runtime.lastError;
+ delete privates(targetChrome.runtime).accessedLastError;
+};
+
+function assertRuntimeIsAvailable() {
+ // chrome.runtime should always be available, but maybe it's disappeared for
+ // some reason? Add debugging for http://crbug.com/258526.
+ var runtimeAvailability = GetAvailability('runtime.lastError');
+ if (!runtimeAvailability.is_available) {
+ throw new Error('runtime.lastError is not available: ' +
+ runtimeAvailability.message);
+ }
+ if (!chrome.runtime)
+ throw new Error('runtime namespace is null or undefined');
+}
+
+/**
+ * Runs |callback(args)| with last error args as in set().
+ *
+ * The target chrome object is the global object's of the callback, so this
+ * method won't work if the real callback has been wrapped (etc).
+ */
+function run(name, message, stack, callback, args) {
+ var global = GetGlobal(callback);
+ var targetChrome = global && global.chrome;
+ set(name, message, stack, targetChrome);
+ try {
+ $Function.apply(callback, undefined, args);
+ } finally {
+ reportIfUnchecked(name, targetChrome, stack);
+ clear(targetChrome);
+ }
+}
+
+/**
+ * Checks whether chrome.runtime.lastError has been accessed if set.
+ * If it was set but not accessed, the error is reported to the console.
+ *
+ * @param {string=} name - name of API.
+ * @param {Object} targetChrome - the Chrome object to check.
+ * @param {string=} stack - Stack trace of the call up to the error.
+ */
+function reportIfUnchecked(name, targetChrome, stack) {
+ if (hasAccessed(targetChrome) || !hasError(targetChrome))
+ return;
+ var message = targetChrome.runtime.lastError.message;
+ console.error("Unchecked runtime.lastError while running " +
+ (name || "unknown") + ": " + message + (stack ? "\n" + stack : ""));
+}
+
+exports.$set('clear', clear);
+exports.$set('hasAccessed', hasAccessed);
+exports.$set('hasError', hasError);
+exports.$set('set', set);
+exports.$set('run', run);
+exports.$set('reportIfUnchecked', reportIfUnchecked);
diff --git a/chromium/extensions/renderer/resources/media_router_bindings.js b/chromium/extensions/renderer/resources/media_router_bindings.js
new file mode 100644
index 00000000000..4eaee9f0286
--- /dev/null
+++ b/chromium/extensions/renderer/resources/media_router_bindings.js
@@ -0,0 +1,777 @@
+// Copyright 2015 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.
+
+var mediaRouter;
+
+define('media_router_bindings', [
+ 'mojo/public/js/bindings',
+ 'mojo/public/js/core',
+ 'content/public/renderer/frame_service_registry',
+ 'chrome/browser/media/router/mojo/media_router.mojom',
+ 'extensions/common/mojo/keep_alive.mojom',
+ 'mojo/public/js/connection',
+ 'mojo/public/js/router',
+], function(bindings,
+ core,
+ serviceProvider,
+ mediaRouterMojom,
+ keepAliveMojom,
+ connector,
+ routerModule) {
+ 'use strict';
+
+ /**
+ * Converts a media sink to a MediaSink Mojo object.
+ * @param {!MediaSink} sink A media sink.
+ * @return {!mediaRouterMojom.MediaSink} A Mojo MediaSink object.
+ */
+ function sinkToMojo_(sink) {
+ return new mediaRouterMojom.MediaSink({
+ 'name': sink.friendlyName,
+ 'description': sink.description,
+ 'domain': sink.domain,
+ 'sink_id': sink.id,
+ 'icon_type': sinkIconTypeToMojo(sink.iconType),
+ });
+ }
+
+ /**
+ * Converts a media sink's icon type to a MediaSink.IconType Mojo object.
+ * @param {!MediaSink.IconType} type A media sink's icon type.
+ * @return {!mediaRouterMojom.MediaSink.IconType} A Mojo MediaSink.IconType
+ * object.
+ */
+ function sinkIconTypeToMojo(type) {
+ switch (type) {
+ case 'cast':
+ return mediaRouterMojom.MediaSink.IconType.CAST;
+ case 'cast_audio':
+ return mediaRouterMojom.MediaSink.IconType.CAST_AUDIO;
+ case 'cast_audio_group':
+ return mediaRouterMojom.MediaSink.IconType.CAST_AUDIO_GROUP;
+ case 'generic':
+ return mediaRouterMojom.MediaSink.IconType.GENERIC;
+ case 'hangout':
+ return mediaRouterMojom.MediaSink.IconType.HANGOUT;
+ default:
+ console.error('Unknown sink icon type : ' + type);
+ return mediaRouterMojom.MediaSink.IconType.GENERIC;
+ }
+ }
+
+ /**
+ * Returns a Mojo MediaRoute object given a MediaRoute and a
+ * media sink name.
+ * @param {!MediaRoute} route
+ * @return {!mojo.MediaRoute}
+ */
+ function routeToMojo_(route) {
+ return new mediaRouterMojom.MediaRoute({
+ 'media_route_id': route.id,
+ 'media_source': route.mediaSource,
+ 'media_sink_id': route.sinkId,
+ 'description': route.description,
+ 'icon_url': route.iconUrl,
+ 'is_local': route.isLocal,
+ 'custom_controller_path': route.customControllerPath,
+ // Begin newly added properties, followed by the milestone they were
+ // added. The guard should be safe to remove N+2 milestones later.
+ 'for_display': route.forDisplay, // M47
+ 'off_the_record': !!route.offTheRecord // M50
+ });
+ }
+
+ /**
+ * Converts a route message to a RouteMessage Mojo object.
+ * @param {!RouteMessage} message
+ * @return {!mediaRouterMojom.RouteMessage} A Mojo RouteMessage object.
+ */
+ function messageToMojo_(message) {
+ if ("string" == typeof message.message) {
+ return new mediaRouterMojom.RouteMessage({
+ 'type': mediaRouterMojom.RouteMessage.Type.TEXT,
+ 'message': message.message,
+ });
+ } else {
+ return new mediaRouterMojom.RouteMessage({
+ 'type': mediaRouterMojom.RouteMessage.Type.BINARY,
+ 'data': message.message,
+ });
+ }
+ }
+
+ /**
+ * Converts presentation connection state to Mojo enum value.
+ * @param {!string} state
+ * @return {!mediaRouterMojom.MediaRouter.PresentationConnectionState}
+ */
+ function presentationConnectionStateToMojo_(state) {
+ var PresentationConnectionState =
+ mediaRouterMojom.MediaRouter.PresentationConnectionState;
+ switch (state) {
+ case 'connecting':
+ return PresentationConnectionState.CONNECTING;
+ case 'connected':
+ return PresentationConnectionState.CONNECTED;
+ case 'closed':
+ return PresentationConnectionState.CLOSED;
+ case 'terminated':
+ return PresentationConnectionState.TERMINATED;
+ default:
+ console.error('Unknown presentation connection state: ' + state);
+ return PresentationConnectionState.TERMINATED;
+ }
+ }
+
+ /**
+ * Converts presentation connection close reason to Mojo enum value.
+ * @param {!string} reason
+ * @return {!mediaRouterMojom.MediaRouter.PresentationConnectionCloseReason}
+ */
+ function presentationConnectionCloseReasonToMojo_(reason) {
+ var PresentationConnectionCloseReason =
+ mediaRouterMojom.MediaRouter.PresentationConnectionCloseReason;
+ switch (reason) {
+ case 'error':
+ return PresentationConnectionCloseReason.CONNECTION_ERROR;
+ case 'closed':
+ return PresentationConnectionCloseReason.CLOSED;
+ case 'went_away':
+ return PresentationConnectionCloseReason.WENT_AWAY;
+ default:
+ console.error('Unknown presentation connection close reason : ' +
+ reason);
+ return PresentationConnectionCloseReason.CONNECTION_ERROR;
+ }
+ }
+
+ /**
+ * Parses the given route request Error object and converts it to the
+ * corresponding result code.
+ * @param {!Error} error
+ * @return {!mediaRouterMojom.RouteRequestResultCode}
+ */
+ function getRouteRequestResultCode_(error) {
+ if (error.message.startsWith('timeout'))
+ return mediaRouterMojom.RouteRequestResultCode.TIMED_OUT;
+ else
+ return mediaRouterMojom.RouteRequestResultCode.UNKNOWN_ERROR;
+ }
+
+ /**
+ * Creates and returns a successful route response from given route.
+ * @param {!MediaRoute} route
+ * @return {!Object}
+ */
+ function toSuccessRouteResponse_(route) {
+ return {
+ route: routeToMojo_(route),
+ result_code: mediaRouterMojom.RouteRequestResultCode.OK
+ };
+ }
+
+ /**
+ * Creates and returns a error route response from given Error object
+ * @param {!Error} error
+ * @return {!Object}
+ */
+ function toErrorRouteResponse_(error) {
+ return {
+ error_text: 'Error creating route: ' + error.message,
+ result_code: getRouteRequestResultCode_(error)
+ };
+ }
+
+ /**
+ * Creates a new MediaRouter.
+ * Converts a route struct to its Mojo form.
+ * @param {!MediaRouterService} service
+ * @constructor
+ */
+ function MediaRouter(service) {
+ /**
+ * The Mojo service proxy. Allows extension code to call methods that reside
+ * in the browser.
+ * @type {!MediaRouterService}
+ */
+ this.service_ = service;
+
+ /**
+ * The provider manager service delegate. Its methods are called by the
+ * browser-resident Mojo service.
+ * @type {!MediaRouter}
+ */
+ this.mrpm_ = new MediaRouteProvider(this);
+
+ /**
+ * The message pipe that connects the Media Router to mrpm_ across
+ * browser/renderer IPC boundaries. Object must remain in scope for the
+ * lifetime of the connection to prevent the connection from closing
+ * automatically.
+ * @type {!mojo.MessagePipe}
+ */
+ this.pipe_ = core.createMessagePipe();
+
+ /**
+ * Handle to a KeepAlive service object, which prevents the extension from
+ * being suspended as long as it remains in scope.
+ * @type {boolean}
+ */
+ this.keepAlive_ = null;
+
+ /**
+ * The stub used to bind the service delegate to the Mojo interface.
+ * Object must remain in scope for the lifetime of the connection to
+ * prevent the connection from closing automatically.
+ * @type {!mojom.MediaRouter}
+ */
+ this.mediaRouteProviderStub_ = connector.bindHandleToStub(
+ this.pipe_.handle0, mediaRouterMojom.MediaRouteProvider);
+
+ // Link mediaRouteProviderStub_ to the provider manager delegate.
+ bindings.StubBindings(this.mediaRouteProviderStub_).delegate = this.mrpm_;
+ }
+
+ /**
+ * Registers the Media Router Provider Manager with the Media Router.
+ * @return {!Promise<string>} Instance ID for the Media Router.
+ */
+ MediaRouter.prototype.start = function() {
+ return this.service_.registerMediaRouteProvider(this.pipe_.handle1).then(
+ function(result) {
+ return result.instance_id;
+ }.bind(this));
+ }
+
+ /**
+ * Sets the service delegate methods.
+ * @param {Object} handlers
+ */
+ MediaRouter.prototype.setHandlers = function(handlers) {
+ this.mrpm_.setHandlers(handlers);
+ }
+
+ /**
+ * The keep alive status.
+ * @return {boolean}
+ */
+ MediaRouter.prototype.getKeepAlive = function() {
+ return this.keepAlive_ != null;
+ };
+
+ /**
+ * Called by the provider manager when a sink list for a given source is
+ * updated.
+ * @param {!string} sourceUrn
+ * @param {!Array<!MediaSink>} sinks
+ * @param {Array<string>=} opt_origins
+ */
+ MediaRouter.prototype.onSinksReceived = function(sourceUrn, sinks,
+ opt_origins) {
+ // TODO(imcheng): Make origins required in M52+.
+ this.service_.onSinksReceived(sourceUrn, sinks.map(sinkToMojo_),
+ opt_origins || []);
+ };
+
+ /**
+ * Called by the provider manager to keep the extension from suspending
+ * if it enters a state where suspension is undesirable (e.g. there is an
+ * active MediaRoute.)
+ * If keepAlive is true, the extension is kept alive.
+ * If keepAlive is false, the extension is allowed to suspend.
+ * @param {boolean} keepAlive
+ */
+ MediaRouter.prototype.setKeepAlive = function(keepAlive) {
+ if (keepAlive === false && this.keepAlive_) {
+ this.keepAlive_.close();
+ this.keepAlive_ = null;
+ } else if (keepAlive === true && !this.keepAlive_) {
+ this.keepAlive_ = new routerModule.Router(
+ serviceProvider.connectToService(
+ keepAliveMojom.KeepAlive.name));
+ }
+ };
+
+ /**
+ * Called by the provider manager to send an issue from a media route
+ * provider to the Media Router, to show the user.
+ * @param {!Object} issue The issue object.
+ */
+ MediaRouter.prototype.onIssue = function(issue) {
+ function issueSeverityToMojo_(severity) {
+ switch (severity) {
+ case 'fatal':
+ return mediaRouterMojom.Issue.Severity.FATAL;
+ case 'warning':
+ return mediaRouterMojom.Issue.Severity.WARNING;
+ case 'notification':
+ return mediaRouterMojom.Issue.Severity.NOTIFICATION;
+ default:
+ console.error('Unknown issue severity: ' + severity);
+ return mediaRouterMojom.Issue.Severity.NOTIFICATION;
+ }
+ }
+
+ function issueActionToMojo_(action) {
+ switch (action) {
+ case 'ok':
+ return mediaRouterMojom.Issue.ActionType.OK;
+ case 'cancel':
+ return mediaRouterMojom.Issue.ActionType.CANCEL;
+ case 'dismiss':
+ return mediaRouterMojom.Issue.ActionType.DISMISS;
+ case 'learn_more':
+ return mediaRouterMojom.Issue.ActionType.LEARN_MORE;
+ default:
+ console.error('Unknown issue action type : ' + action);
+ return mediaRouterMojom.Issue.ActionType.OK;
+ }
+ }
+
+ var secondaryActions = (issue.secondaryActions || []).map(function(e) {
+ return issueActionToMojo_(e);
+ });
+ this.service_.onIssue(new mediaRouterMojom.Issue({
+ 'route_id': issue.routeId,
+ 'severity': issueSeverityToMojo_(issue.severity),
+ 'title': issue.title,
+ 'message': issue.message,
+ 'default_action': issueActionToMojo_(issue.defaultAction),
+ 'secondary_actions': secondaryActions,
+ 'help_url': issue.helpUrl,
+ 'is_blocking': issue.isBlocking
+ }));
+ };
+
+ /**
+ * Called by the provider manager when the set of active routes
+ * has been updated.
+ * @param {!Array<MediaRoute>} routes The active set of media routes.
+ * @param {string=} opt_sourceUrn The sourceUrn associated with this route
+ * query. This parameter is optional and can be empty.
+ * @param {Array<string>=} opt_joinableRouteIds The active set of joinable
+ * media routes. This parameter is optional and can be empty.
+ */
+ MediaRouter.prototype.onRoutesUpdated =
+ function(routes, opt_sourceUrn, opt_joinableRouteIds) {
+ // TODO(boetger): This check allows backward compatibility with the Cast SDK
+ // and can be removed when the Cast SDK is updated.
+ if (typeof(opt_sourceUrn) != 'string') {
+ opt_sourceUrn = '';
+ }
+
+ this.service_.onRoutesUpdated(
+ routes.map(routeToMojo_),
+ opt_sourceUrn || '',
+ opt_joinableRouteIds || []);
+ };
+
+ /**
+ * Called by the provider manager when sink availability has been updated.
+ * @param {!mediaRouterMojom.MediaRouter.SinkAvailability} availability
+ * The new sink availability.
+ */
+ MediaRouter.prototype.onSinkAvailabilityUpdated = function(availability) {
+ this.service_.onSinkAvailabilityUpdated(availability);
+ };
+
+ /**
+ * Called by the provider manager when the state of a presentation connected
+ * to a route has changed.
+ * @param {string} routeId
+ * @param {string} state
+ */
+ MediaRouter.prototype.onPresentationConnectionStateChanged =
+ function(routeId, state) {
+ this.service_.onPresentationConnectionStateChanged(
+ routeId, presentationConnectionStateToMojo_(state));
+ };
+
+ /**
+ * Called by the provider manager when the state of a presentation connected
+ * to a route has closed.
+ * @param {string} routeId
+ * @param {string} reason
+ * @param {string} message
+ */
+ MediaRouter.prototype.onPresentationConnectionClosed =
+ function(routeId, reason, message) {
+ this.service_.onPresentationConnectionClosed(
+ routeId, presentationConnectionCloseReasonToMojo_(reason), message);
+ };
+
+ /**
+ * Object containing callbacks set by the provider manager.
+ *
+ * @constructor
+ * @struct
+ */
+ function MediaRouterHandlers() {
+ /**
+ * @type {function(!string, !string, !string, !string, !number}
+ */
+ this.createRoute = null;
+
+ /**
+ * @type {function(!string, !string, !string, !number)}
+ */
+ this.joinRoute = null;
+
+ /**
+ * @type {function(string)}
+ */
+ this.terminateRoute = null;
+
+ /**
+ * @type {function(string)}
+ */
+ this.startObservingMediaSinks = null;
+
+ /**
+ * @type {function(string)}
+ */
+ this.stopObservingMediaSinks = null;
+
+ /**
+ * @type {function(string, string): Promise}
+ */
+ this.sendRouteMessage = null;
+
+ /**
+ * @type {function(string, Uint8Array): Promise}
+ */
+ this.sendRouteBinaryMessage = null;
+
+ /**
+ * @type {function(string):
+ * Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
+ */
+ this.listenForRouteMessages = null;
+
+ /**
+ * @type {function(string)}
+ */
+ this.stopListeningForRouteMessages = null;
+
+ /**
+ * @type {function(string)}
+ */
+ this.detachRoute = null;
+
+ /**
+ * @type {function()}
+ */
+ this.startObservingMediaRoutes = null;
+
+ /**
+ * @type {function()}
+ */
+ this.stopObservingMediaRoutes = null;
+
+ /**
+ * @type {function()}
+ */
+ this.connectRouteByRouteId = null;
+
+ /**
+ * @type {function()}
+ */
+ this.enableMdnsDiscovery = null;
+
+ /**
+ * @type {function()}
+ */
+ this.updateMediaSinks = null;
+ };
+
+ /**
+ * Routes calls from Media Router to the provider manager extension.
+ * Registered with the MediaRouter stub.
+ * @param {!MediaRouter} MediaRouter proxy to call into the
+ * Media Router mojo interface.
+ * @constructor
+ */
+ function MediaRouteProvider(mediaRouter) {
+ mediaRouterMojom.MediaRouteProvider.stubClass.call(this);
+
+ /**
+ * Object containing JS callbacks into Provider Manager code.
+ * @type {!MediaRouterHandlers}
+ */
+ this.handlers_ = new MediaRouterHandlers();
+
+ /**
+ * Proxy class to the browser's Media Router Mojo service.
+ * @type {!MediaRouter}
+ */
+ this.mediaRouter_ = mediaRouter;
+ }
+ MediaRouteProvider.prototype = Object.create(
+ mediaRouterMojom.MediaRouteProvider.stubClass.prototype);
+
+ /*
+ * Sets the callback handler used to invoke methods in the provider manager.
+ *
+ * @param {!MediaRouterHandlers} handlers
+ */
+ MediaRouteProvider.prototype.setHandlers = function(handlers) {
+ this.handlers_ = handlers;
+ var requiredHandlers = [
+ 'stopObservingMediaRoutes',
+ 'startObservingMediaRoutes',
+ 'sendRouteMessage',
+ 'sendRouteBinaryMessage',
+ 'listenForRouteMessages',
+ 'stopListeningForRouteMessages',
+ 'detachRoute',
+ 'terminateRoute',
+ 'joinRoute',
+ 'createRoute',
+ 'stopObservingMediaSinks',
+ 'startObservingMediaRoutes',
+ 'connectRouteByRouteId',
+ 'enableMdnsDiscovery',
+ 'updateMediaSinks',
+ ];
+ requiredHandlers.forEach(function(nextHandler) {
+ if (handlers[nextHandler] === undefined) {
+ console.error(nextHandler + ' handler not registered.');
+ }
+ });
+ }
+
+ /**
+ * Starts querying for sinks capable of displaying the media source
+ * designated by |sourceUrn|. Results are returned by calling
+ * OnSinksReceived.
+ * @param {!string} sourceUrn
+ */
+ MediaRouteProvider.prototype.startObservingMediaSinks =
+ function(sourceUrn) {
+ this.handlers_.startObservingMediaSinks(sourceUrn);
+ };
+
+ /**
+ * Stops querying for sinks capable of displaying |sourceUrn|.
+ * @param {!string} sourceUrn
+ */
+ MediaRouteProvider.prototype.stopObservingMediaSinks =
+ function(sourceUrn) {
+ this.handlers_.stopObservingMediaSinks(sourceUrn);
+ };
+
+ /**
+ * Requests that |sinkId| render the media referenced by |sourceUrn|. If the
+ * request is from the Presentation API, then origin and tabId will
+ * be populated.
+ * @param {!string} sourceUrn Media source to render.
+ * @param {!string} sinkId Media sink ID.
+ * @param {!string} presentationId Presentation ID from the site
+ * requesting presentation. TODO(mfoltz): Remove.
+ * @param {!string} origin Origin of site requesting presentation.
+ * @param {!number} tabId ID of tab requesting presentation.
+ * @param {!number} timeoutMillis If positive, the timeout duration for the
+ * request, measured in seconds. Otherwise, the default duration will be
+ * used.
+ * @param {!boolean} offTheRecord If true, the route is being requested by
+ * an off the record (incognito) profile.
+ * @return {!Promise.<!Object>} A Promise resolving to an object describing
+ * the newly created media route, or rejecting with an error message on
+ * failure.
+ */
+ MediaRouteProvider.prototype.createRoute =
+ function(sourceUrn, sinkId, presentationId, origin, tabId,
+ timeoutMillis, offTheRecord) {
+ return this.handlers_.createRoute(
+ sourceUrn, sinkId, presentationId, origin, tabId, timeoutMillis,
+ offTheRecord)
+ .then(function(route) {
+ return toSuccessRouteResponse_(route);
+ },
+ function(err) {
+ return toErrorRouteResponse_(err);
+ });
+ };
+
+ /**
+ * Handles a request via the Presentation API to join an existing route given
+ * by |sourceUrn| and |presentationId|. |origin| and |tabId| are used for
+ * validating same-origin/tab scope.
+ * @param {!string} sourceUrn Media source to render.
+ * @param {!string} presentationId Presentation ID to join.
+ * @param {!string} origin Origin of site requesting join.
+ * @param {!number} tabId ID of tab requesting join.
+ * @param {!number} timeoutMillis If positive, the timeout duration for the
+ * request, measured in seconds. Otherwise, the default duration will be
+ * used.
+ * @param {!boolean} offTheRecord If true, the route is being requested by
+ * an off the record (incognito) profile.
+ * @return {!Promise.<!Object>} A Promise resolving to an object describing
+ * the newly created media route, or rejecting with an error message on
+ * failure.
+ */
+ MediaRouteProvider.prototype.joinRoute =
+ function(sourceUrn, presentationId, origin, tabId, timeoutMillis,
+ offTheRecord) {
+ return this.handlers_.joinRoute(
+ sourceUrn, presentationId, origin, tabId, timeoutMillis, offTheRecord)
+ .then(function(route) {
+ return toSuccessRouteResponse_(route);
+ },
+ function(err) {
+ return toErrorRouteResponse_(err);
+ });
+ };
+
+ /**
+ * Handles a request via the Presentation API to join an existing route given
+ * by |sourceUrn| and |routeId|. |origin| and |tabId| are used for
+ * validating same-origin/tab scope.
+ * @param {!string} sourceUrn Media source to render.
+ * @param {!string} routeId Route ID to join.
+ * @param {!string} presentationId Presentation ID to join.
+ * @param {!string} origin Origin of site requesting join.
+ * @param {!number} tabId ID of tab requesting join.
+ * @param {!number} timeoutMillis If positive, the timeout duration for the
+ * request, measured in seconds. Otherwise, the default duration will be
+ * used.
+ * @param {!boolean} offTheRecord If true, the route is being requested by
+ * an off the record (incognito) profile.
+ * @return {!Promise.<!Object>} A Promise resolving to an object describing
+ * the newly created media route, or rejecting with an error message on
+ * failure.
+ */
+ MediaRouteProvider.prototype.connectRouteByRouteId =
+ function(sourceUrn, routeId, presentationId, origin, tabId,
+ timeoutMillis, offTheRecord) {
+ return this.handlers_.connectRouteByRouteId(
+ sourceUrn, routeId, presentationId, origin, tabId, timeoutMillis,
+ offTheRecord)
+ .then(function(route) {
+ return toSuccessRouteResponse_(route);
+ },
+ function(err) {
+ return toErrorRouteResponse_(err);
+ });
+ };
+
+ /**
+ * Terminates the route specified by |routeId|.
+ * @param {!string} routeId
+ */
+ MediaRouteProvider.prototype.terminateRoute = function(routeId) {
+ this.handlers_.terminateRoute(routeId);
+ };
+
+ /**
+ * Posts a message to the route designated by |routeId|.
+ * @param {!string} routeId
+ * @param {!string} message
+ * @return {!Promise.<boolean>} Resolved with true if the message was sent,
+ * or false on failure.
+ */
+ MediaRouteProvider.prototype.sendRouteMessage = function(
+ routeId, message) {
+ return this.handlers_.sendRouteMessage(routeId, message)
+ .then(function() {
+ return {'sent': true};
+ }, function() {
+ return {'sent': false};
+ });
+ };
+
+ /**
+ * Sends a binary message to the route designated by |routeId|.
+ * @param {!string} routeId
+ * @param {!Uint8Array} data
+ * @return {!Promise.<boolean>} Resolved with true if the data was sent,
+ * or false on failure.
+ */
+ MediaRouteProvider.prototype.sendRouteBinaryMessage = function(
+ routeId, data) {
+ return this.handlers_.sendRouteBinaryMessage(routeId, data)
+ .then(function() {
+ return {'sent': true};
+ }, function() {
+ return {'sent': false};
+ });
+ };
+
+ /**
+ * Listen for next batch of messages from one of the routeIds.
+ * @param {!string} routeId
+ * @return {!Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
+ * Resolved with a list of messages, and a boolean indicating if an error
+ * occurred.
+ */
+ MediaRouteProvider.prototype.listenForRouteMessages = function(routeId) {
+ return this.handlers_.listenForRouteMessages(routeId)
+ .then(function(messages) {
+ return {'messages': messages.map(messageToMojo_), 'error': false};
+ }, function() {
+ return {'messages': [], 'error': true};
+ });
+ };
+
+ /**
+ * If there is an outstanding |listenForRouteMessages| promise for
+ * |routeId|, resolve that promise with an empty array.
+ * @param {!string} routeId
+ */
+ MediaRouteProvider.prototype.stopListeningForRouteMessages = function(
+ routeId) {
+ return this.handlers_.stopListeningForRouteMessages(routeId);
+ };
+
+ /**
+ * Indicates that the presentation connection that was connected to |routeId|
+ * is no longer connected to it.
+ * @param {!string} routeId
+ */
+ MediaRouteProvider.prototype.detachRoute = function(
+ routeId) {
+ this.handlers_.detachRoute(routeId);
+ };
+
+ /**
+ * Requests that the provider manager start sending information about active
+ * media routes to the Media Router.
+ * @param {!string} sourceUrn
+ */
+ MediaRouteProvider.prototype.startObservingMediaRoutes = function(sourceUrn) {
+ this.handlers_.startObservingMediaRoutes(sourceUrn);
+ };
+
+ /**
+ * Requests that the provider manager stop sending information about active
+ * media routes to the Media Router.
+ * @param {!string} sourceUrn
+ */
+ MediaRouteProvider.prototype.stopObservingMediaRoutes = function(sourceUrn) {
+ this.handlers_.stopObservingMediaRoutes(sourceUrn);
+ };
+
+ /**
+ * Enables mDNS device discovery.
+ */
+ MediaRouteProvider.prototype.enableMdnsDiscovery = function() {
+ this.handlers_.enableMdnsDiscovery();
+ };
+
+ /**
+ * Requests that the provider manager update media sinks.
+ * @param {!string} sourceUrn
+ */
+ MediaRouteProvider.prototype.updateMediaSinks = function(sourceUrn) {
+ this.handlers_.updateMediaSinks(sourceUrn);
+ };
+
+ mediaRouter = new MediaRouter(connector.bindHandleToProxy(
+ serviceProvider.connectToService(
+ mediaRouterMojom.MediaRouter.name),
+ mediaRouterMojom.MediaRouter));
+
+ return mediaRouter;
+});
+
diff --git a/chromium/extensions/renderer/resources/messaging.js b/chromium/extensions/renderer/resources/messaging.js
new file mode 100644
index 00000000000..8ca84ea4655
--- /dev/null
+++ b/chromium/extensions/renderer/resources/messaging.js
@@ -0,0 +1,446 @@
+// Copyright 2014 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.
+
+// chrome.runtime.messaging API implementation.
+// TODO(robwu): Fix this indentation.
+
+ // TODO(kalman): factor requiring chrome out of here.
+ var chrome = requireNative('chrome').GetChrome();
+ var Event = require('event_bindings').Event;
+ var lastError = require('lastError');
+ var logActivity = requireNative('activityLogger');
+ var logging = requireNative('logging');
+ var messagingNatives = requireNative('messaging_natives');
+ var processNatives = requireNative('process');
+ var utils = require('utils');
+ var messagingUtils = require('messaging_utils');
+
+ // The reserved channel name for the sendRequest/send(Native)Message APIs.
+ // Note: sendRequest is deprecated.
+ var kRequestChannel = "chrome.extension.sendRequest";
+ var kMessageChannel = "chrome.runtime.sendMessage";
+ var kNativeMessageChannel = "chrome.runtime.sendNativeMessage";
+
+ // Map of port IDs to port object.
+ var ports = {__proto__: null};
+
+ // Change even to odd and vice versa, to get the other side of a given
+ // channel.
+ function getOppositePortId(portId) { return portId ^ 1; }
+
+ // Port object. Represents a connection to another script context through
+ // which messages can be passed.
+ function PortImpl(portId, opt_name) {
+ this.portId_ = portId;
+ this.name = opt_name;
+
+ // Note: Keep these schemas in sync with the documentation in runtime.json
+ var portSchema = {
+ __proto__: null,
+ name: 'port',
+ $ref: 'runtime.Port',
+ };
+ var messageSchema = {
+ __proto__: null,
+ name: 'message',
+ type: 'any',
+ optional: true,
+ };
+ var options = {
+ __proto__: null,
+ unmanaged: true,
+ };
+ this.onDisconnect = new Event(null, [portSchema], options);
+ this.onMessage = new Event(null, [messageSchema, portSchema], options);
+ this.onDestroy_ = null;
+ }
+ $Object.setPrototypeOf(PortImpl.prototype, null);
+
+ // Sends a message asynchronously to the context on the other end of this
+ // port.
+ PortImpl.prototype.postMessage = function(msg) {
+ // JSON.stringify doesn't support a root object which is undefined.
+ if (msg === undefined)
+ msg = null;
+ msg = $JSON.stringify(msg);
+ if (msg === undefined) {
+ // JSON.stringify can fail with unserializable objects. Log an error and
+ // drop the message.
+ //
+ // TODO(kalman/mpcomplete): it would be better to do the same validation
+ // here that we do for runtime.sendMessage (and variants), i.e. throw an
+ // schema validation Error, but just maintain the old behaviour until
+ // there's a good reason not to (http://crbug.com/263077).
+ console.error('Illegal argument to Port.postMessage');
+ return;
+ }
+ messagingNatives.PostMessage(this.portId_, msg);
+ };
+
+ // Disconnects the port from the other end.
+ PortImpl.prototype.disconnect = function() {
+ messagingNatives.CloseChannel(this.portId_, true);
+ this.destroy_();
+ };
+
+ PortImpl.prototype.destroy_ = function() {
+ if (this.onDestroy_) {
+ this.onDestroy_();
+ this.onDestroy_ = null;
+ }
+ privates(this.onDisconnect).impl.destroy_();
+ privates(this.onMessage).impl.destroy_();
+ // TODO(robwu): Remove port lifetime management because it is completely
+ // handled in the browser. The renderer's only roles are
+ // 1) rejecting ports so that the browser knows that the renderer is not
+ // interested in the port (this is merely an optimization)
+ // 2) acknowledging port creations, so that the browser knows that the port
+ // was successfully created (from the perspective of the extension), but
+ // then closed for some non-fatal reason.
+ // 3) notifying the browser of explicit port closure via .disconnect().
+ // In other cases (navigations), the browser automatically cleans up the
+ // port.
+ messagingNatives.PortRelease(this.portId_);
+ delete ports[this.portId_];
+ };
+
+ // Returns true if the specified port id is in this context. This is used by
+ // the C++ to avoid creating the javascript message for all the contexts that
+ // don't care about a particular message.
+ function hasPort(portId) {
+ return portId in ports;
+ };
+
+ // Hidden port creation function. We don't want to expose an API that lets
+ // people add arbitrary port IDs to the port list.
+ function createPort(portId, opt_name) {
+ if (ports[portId])
+ throw new Error("Port '" + portId + "' already exists.");
+ var port = new Port(portId, opt_name);
+ ports[portId] = port;
+ messagingNatives.PortAddRef(portId);
+ return port;
+ };
+
+ // Helper function for dispatchOnRequest.
+ function handleSendRequestError(isSendMessage,
+ responseCallbackPreserved,
+ sourceExtensionId,
+ targetExtensionId,
+ sourceUrl) {
+ var errorMsg;
+ var eventName = isSendMessage ? 'runtime.onMessage' : 'extension.onRequest';
+ if (isSendMessage && !responseCallbackPreserved) {
+ errorMsg =
+ 'The chrome.' + eventName + ' listener must return true if you ' +
+ 'want to send a response after the listener returns';
+ } else {
+ errorMsg =
+ 'Cannot send a response more than once per chrome.' + eventName +
+ ' listener per document';
+ }
+ errorMsg += ' (message was sent by extension' + sourceExtensionId;
+ if (sourceExtensionId && sourceExtensionId !== targetExtensionId)
+ errorMsg += ' for extension ' + targetExtensionId;
+ if (sourceUrl)
+ errorMsg += ' for URL ' + sourceUrl;
+ errorMsg += ').';
+ lastError.set(eventName, errorMsg, null, chrome);
+ }
+
+ // Helper function for dispatchOnConnect
+ function dispatchOnRequest(portId, channelName, sender,
+ sourceExtensionId, targetExtensionId, sourceUrl,
+ isExternal) {
+ var isSendMessage = channelName == kMessageChannel;
+ var requestEvent = null;
+ if (isSendMessage) {
+ if (chrome.runtime) {
+ requestEvent = isExternal ? chrome.runtime.onMessageExternal
+ : chrome.runtime.onMessage;
+ }
+ } else {
+ if (chrome.extension) {
+ requestEvent = isExternal ? chrome.extension.onRequestExternal
+ : chrome.extension.onRequest;
+ }
+ }
+ if (!requestEvent)
+ return false;
+ if (!requestEvent.hasListeners())
+ return false;
+ var port = createPort(portId, channelName);
+
+ function messageListener(request) {
+ var responseCallbackPreserved = false;
+ var responseCallback = function(response) {
+ if (port) {
+ port.postMessage(response);
+ privates(port).impl.destroy_();
+ port = null;
+ } else {
+ // We nulled out port when sending the response, and now the page
+ // is trying to send another response for the same request.
+ handleSendRequestError(isSendMessage, responseCallbackPreserved,
+ sourceExtensionId, targetExtensionId);
+ }
+ };
+ // In case the extension never invokes the responseCallback, and also
+ // doesn't keep a reference to it, we need to clean up the port. Do
+ // so by attaching to the garbage collection of the responseCallback
+ // using some native hackery.
+ //
+ // If the context is destroyed before this has a chance to execute,
+ // BindToGC knows to release |portId| (important for updating C++ state
+ // both in this renderer and on the other end). We don't need to clear
+ // any JavaScript state, as calling destroy_() would usually do - but
+ // the context has been destroyed, so there isn't any JS state to clear.
+ messagingNatives.BindToGC(responseCallback, function() {
+ if (port) {
+ privates(port).impl.destroy_();
+ port = null;
+ }
+ }, portId);
+ var rv = requestEvent.dispatch(request, sender, responseCallback);
+ if (isSendMessage) {
+ responseCallbackPreserved =
+ rv && rv.results && $Array.indexOf(rv.results, true) > -1;
+ if (!responseCallbackPreserved && port) {
+ // If they didn't access the response callback, they're not
+ // going to send a response, so clean up the port immediately.
+ privates(port).impl.destroy_();
+ port = null;
+ }
+ }
+ }
+
+ privates(port).impl.onDestroy_ = function() {
+ port.onMessage.removeListener(messageListener);
+ };
+ port.onMessage.addListener(messageListener);
+
+ var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest";
+ if (isExternal)
+ eventName += "External";
+ logActivity.LogEvent(targetExtensionId,
+ eventName,
+ [sourceExtensionId, sourceUrl]);
+ return true;
+ }
+
+ // Called by native code when a channel has been opened to this context.
+ function dispatchOnConnect(portId,
+ channelName,
+ sourceTab,
+ sourceFrameId,
+ guestProcessId,
+ guestRenderFrameRoutingId,
+ sourceExtensionId,
+ targetExtensionId,
+ sourceUrl,
+ tlsChannelId) {
+ // Only create a new Port if someone is actually listening for a connection.
+ // In addition to being an optimization, this also fixes a bug where if 2
+ // channels were opened to and from the same process, closing one would
+ // close both.
+ var extensionId = processNatives.GetExtensionId();
+
+ // messaging_bindings.cc should ensure that this method only gets called for
+ // the right extension.
+ logging.CHECK(targetExtensionId == extensionId);
+
+ if (ports[getOppositePortId(portId)])
+ return false; // this channel was opened by us, so ignore it
+
+ // Determine whether this is coming from another extension, so we can use
+ // the right event.
+ var isExternal = sourceExtensionId != extensionId;
+
+ var sender = {};
+ if (sourceExtensionId != '')
+ sender.id = sourceExtensionId;
+ if (sourceUrl)
+ sender.url = sourceUrl;
+ if (sourceTab)
+ sender.tab = sourceTab;
+ if (sourceFrameId >= 0)
+ sender.frameId = sourceFrameId;
+ if (typeof guestProcessId !== 'undefined' &&
+ typeof guestRenderFrameRoutingId !== 'undefined') {
+ // Note that |guestProcessId| and |guestRenderFrameRoutingId| are not
+ // standard fields on MessageSender and should not be exposed to drive-by
+ // extensions; it is only exposed to component extensions.
+ logging.CHECK(processNatives.IsComponentExtension(),
+ "GuestProcessId can only be exposed to component extensions.");
+ sender.guestProcessId = guestProcessId;
+ sender.guestRenderFrameRoutingId = guestRenderFrameRoutingId;
+ }
+ if (typeof tlsChannelId != 'undefined')
+ sender.tlsChannelId = tlsChannelId;
+
+ // Special case for sendRequest/onRequest and sendMessage/onMessage.
+ if (channelName == kRequestChannel || channelName == kMessageChannel) {
+ return dispatchOnRequest(portId, channelName, sender,
+ sourceExtensionId, targetExtensionId, sourceUrl,
+ isExternal);
+ }
+
+ var connectEvent = null;
+ if (chrome.runtime) {
+ connectEvent = isExternal ? chrome.runtime.onConnectExternal
+ : chrome.runtime.onConnect;
+ }
+ if (!connectEvent)
+ return false;
+ if (!connectEvent.hasListeners())
+ return false;
+
+ var port = createPort(portId, channelName);
+ port.sender = sender;
+ if (processNatives.manifestVersion < 2)
+ port.tab = port.sender.tab;
+
+ var eventName = (isExternal ?
+ "runtime.onConnectExternal" : "runtime.onConnect");
+ connectEvent.dispatch(port);
+ logActivity.LogEvent(targetExtensionId,
+ eventName,
+ [sourceExtensionId]);
+ return true;
+ };
+
+ // Called by native code when a channel has been closed.
+ function dispatchOnDisconnect(portId, errorMessage) {
+ var port = ports[portId];
+ if (port) {
+ // Update the renderer's port bookkeeping, without notifying the browser.
+ messagingNatives.CloseChannel(portId, false);
+ if (errorMessage)
+ lastError.set('Port', errorMessage, null, chrome);
+ try {
+ port.onDisconnect.dispatch(port);
+ } finally {
+ privates(port).impl.destroy_();
+ lastError.clear(chrome);
+ }
+ }
+ };
+
+ // Called by native code when a message has been sent to the given port.
+ function dispatchOnMessage(msg, portId) {
+ var port = ports[portId];
+ if (port) {
+ if (msg)
+ msg = $JSON.parse(msg);
+ port.onMessage.dispatch(msg, port);
+ }
+ };
+
+ // Shared implementation used by tabs.sendMessage and runtime.sendMessage.
+ function sendMessageImpl(port, request, responseCallback) {
+ if (port.name != kNativeMessageChannel)
+ port.postMessage(request);
+
+ if (port.name == kMessageChannel && !responseCallback) {
+ // TODO(mpcomplete): Do this for the old sendRequest API too, after
+ // verifying it doesn't break anything.
+ // Go ahead and disconnect immediately if the sender is not expecting
+ // a response.
+ port.disconnect();
+ return;
+ }
+
+ function sendResponseAndClearCallback(response) {
+ // Save a reference so that we don't re-entrantly call responseCallback.
+ var sendResponse = responseCallback;
+ responseCallback = null;
+ if (arguments.length === 0) {
+ // According to the documentation of chrome.runtime.sendMessage, the
+ // callback is invoked without any arguments when an error occurs.
+ sendResponse();
+ } else {
+ sendResponse(response);
+ }
+ }
+
+
+ // Note: make sure to manually remove the onMessage/onDisconnect listeners
+ // that we added before destroying the Port, a workaround to a bug in Port
+ // where any onMessage/onDisconnect listeners added but not removed will
+ // be leaked when the Port is destroyed.
+ // http://crbug.com/320723 tracks a sustainable fix.
+
+ function disconnectListener() {
+ if (!responseCallback)
+ return;
+
+ if (lastError.hasError(chrome)) {
+ sendResponseAndClearCallback();
+ } else {
+ lastError.set(
+ port.name, 'The message port closed before a reponse was received.',
+ null, chrome);
+ try {
+ sendResponseAndClearCallback();
+ } finally {
+ lastError.clear(chrome);
+ }
+ }
+ }
+
+ function messageListener(response) {
+ try {
+ if (responseCallback)
+ sendResponseAndClearCallback(response);
+ } finally {
+ port.disconnect();
+ }
+ }
+
+ privates(port).impl.onDestroy_ = function() {
+ port.onDisconnect.removeListener(disconnectListener);
+ port.onMessage.removeListener(messageListener);
+ };
+ port.onDisconnect.addListener(disconnectListener);
+ port.onMessage.addListener(messageListener);
+ };
+
+ function sendMessageUpdateArguments(functionName, hasOptionsArgument) {
+ // skip functionName and hasOptionsArgument
+ var args = $Array.slice(arguments, 2);
+ var alignedArgs = messagingUtils.alignSendMessageArguments(args,
+ hasOptionsArgument);
+ if (!alignedArgs)
+ throw new Error('Invalid arguments to ' + functionName + '.');
+ return alignedArgs;
+ }
+
+ function Port() {
+ privates(Port).constructPrivate(this, arguments);
+ }
+ utils.expose(Port, PortImpl, {
+ functions: [
+ 'disconnect',
+ 'postMessage',
+ ],
+ properties: [
+ 'name',
+ 'onDisconnect',
+ 'onMessage',
+ ],
+ });
+
+exports.$set('kRequestChannel', kRequestChannel);
+exports.$set('kMessageChannel', kMessageChannel);
+exports.$set('kNativeMessageChannel', kNativeMessageChannel);
+exports.$set('Port', Port);
+exports.$set('createPort', createPort);
+exports.$set('sendMessageImpl', sendMessageImpl);
+exports.$set('sendMessageUpdateArguments', sendMessageUpdateArguments);
+
+// For C++ code to call.
+exports.$set('hasPort', hasPort);
+exports.$set('dispatchOnConnect', dispatchOnConnect);
+exports.$set('dispatchOnDisconnect', dispatchOnDisconnect);
+exports.$set('dispatchOnMessage', dispatchOnMessage);
diff --git a/chromium/extensions/renderer/resources/messaging_utils.js b/chromium/extensions/renderer/resources/messaging_utils.js
new file mode 100644
index 00000000000..381fbeb572b
--- /dev/null
+++ b/chromium/extensions/renderer/resources/messaging_utils.js
@@ -0,0 +1,53 @@
+// Copyright 2014 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.
+
+// Routines used to normalize arguments to messaging functions.
+
+function alignSendMessageArguments(args, hasOptionsArgument) {
+ // Align missing (optional) function arguments with the arguments that
+ // schema validation is expecting, e.g.
+ // extension.sendRequest(req) -> extension.sendRequest(null, req)
+ // extension.sendRequest(req, cb) -> extension.sendRequest(null, req, cb)
+ if (!args || !args.length)
+ return null;
+ var lastArg = args.length - 1;
+
+ // responseCallback (last argument) is optional.
+ var responseCallback = null;
+ if (typeof args[lastArg] == 'function')
+ responseCallback = args[lastArg--];
+
+ var options = null;
+ if (hasOptionsArgument && lastArg >= 1) {
+ // options (third argument) is optional. It can also be ambiguous which
+ // argument it should match. If there are more than two arguments remaining,
+ // options is definitely present:
+ if (lastArg > 1) {
+ options = args[lastArg--];
+ } else {
+ // Exactly two arguments remaining. If the first argument is a string,
+ // it should bind to targetId, and the second argument should bind to
+ // request, which is required. In other words, when two arguments remain,
+ // only bind options when the first argument cannot bind to targetId.
+ if (!(args[0] === null || typeof args[0] == 'string'))
+ options = args[lastArg--];
+ }
+ }
+
+ // request (second argument) is required.
+ var request = args[lastArg--];
+
+ // targetId (first argument, extensionId in the manifest) is optional.
+ var targetId = null;
+ if (lastArg >= 0)
+ targetId = args[lastArg--];
+
+ if (lastArg != -1)
+ return null;
+ if (hasOptionsArgument)
+ return [targetId, request, options, responseCallback];
+ return [targetId, request, responseCallback];
+}
+
+exports.$set('alignSendMessageArguments', alignSendMessageArguments);
diff --git a/chromium/extensions/renderer/resources/mime_handler_private_custom_bindings.js b/chromium/extensions/renderer/resources/mime_handler_private_custom_bindings.js
new file mode 100644
index 00000000000..8e9eb20ebe5
--- /dev/null
+++ b/chromium/extensions/renderer/resources/mime_handler_private_custom_bindings.js
@@ -0,0 +1,75 @@
+// Copyright 2015 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.
+
+/**
+ * Custom bindings for the mime handler API.
+ */
+
+var binding = require('binding').Binding.create('mimeHandlerPrivate');
+
+var NO_STREAM_ERROR =
+ 'Streams are only available from a mime handler view guest.';
+var STREAM_ABORTED_ERROR = 'Stream has been aborted.';
+
+var servicePromise = Promise.all([
+ requireAsync('content/public/renderer/frame_service_registry'),
+ requireAsync('extensions/common/api/mime_handler.mojom'),
+ requireAsync('mojo/public/js/router'),
+]).then(function(modules) {
+ var serviceProvider = modules[0];
+ var mojom = modules[1];
+ var routerModule = modules[2];
+ return new mojom.MimeHandlerService.proxyClass(new routerModule.Router(
+ serviceProvider.connectToService(mojom.MimeHandlerService.name)));
+});
+
+// Stores a promise to the GetStreamInfo() result to avoid making additional
+// calls in response to getStreamInfo() calls.
+var streamInfoPromise;
+
+function throwNoStreamError() {
+ throw new Error(NO_STREAM_ERROR);
+}
+
+function createStreamInfoPromise() {
+ return servicePromise.then(function(service) {
+ return service.getStreamInfo();
+ }).then(function(result) {
+ if (!result.stream_info)
+ throw new Error(STREAM_ABORTED_ERROR);
+ return result.stream_info;
+ }, throwNoStreamError);
+}
+
+function constructStreamInfoDict(streamInfo) {
+ var headers = {};
+ for (var header of streamInfo.response_headers) {
+ headers[header[0]] = header[1];
+ }
+ return {
+ mimeType: streamInfo.mime_type,
+ originalUrl: streamInfo.original_url,
+ streamUrl: streamInfo.stream_url,
+ tabId: streamInfo.tab_id,
+ embedded: !!streamInfo.embedded,
+ responseHeaders: headers,
+ };
+}
+
+binding.registerCustomHook(function(bindingsAPI) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+ apiFunctions.setHandleRequestWithPromise('getStreamInfo', function() {
+ if (!streamInfoPromise)
+ streamInfoPromise = createStreamInfoPromise();
+ return streamInfoPromise.then(constructStreamInfoDict);
+ });
+
+ apiFunctions.setHandleRequestWithPromise('abortStream', function() {
+ return servicePromise.then(function(service) {
+ return service.abortStream().then(function() {});
+ }).catch(throwNoStreamError);
+ });
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/mojo_private_custom_bindings.js b/chromium/extensions/renderer/resources/mojo_private_custom_bindings.js
new file mode 100644
index 00000000000..18d6016ba3b
--- /dev/null
+++ b/chromium/extensions/renderer/resources/mojo_private_custom_bindings.js
@@ -0,0 +1,23 @@
+// Copyright 2015 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.
+
+/**
+ * Custom bindings for the mojoPrivate API.
+ */
+
+let binding = require('binding').Binding.create('mojoPrivate');
+
+binding.registerCustomHook(function(bindingsAPI) {
+ let apiFunctions = bindingsAPI.apiFunctions;
+
+ apiFunctions.setHandleRequest('define', function(name, deps, factory) {
+ define(name, deps || [], factory);
+ });
+
+ apiFunctions.setHandleRequest('requireAsync', function(moduleName) {
+ return requireAsync(moduleName);
+ });
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/permissions_custom_bindings.js b/chromium/extensions/renderer/resources/permissions_custom_bindings.js
new file mode 100644
index 00000000000..492360a9ef1
--- /dev/null
+++ b/chromium/extensions/renderer/resources/permissions_custom_bindings.js
@@ -0,0 +1,92 @@
+// Copyright 2014 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.
+
+// Custom binding for the Permissions API.
+
+var binding = require('binding').Binding.create('permissions');
+
+var Event = require('event_bindings').Event;
+
+// These custom binding are only necessary because it is not currently
+// possible to have a union of types as the type of the items in an array.
+// Once that is fixed, this entire file should go away.
+// See,
+// https://code.google.com/p/chromium/issues/detail?id=162044
+// https://code.google.com/p/chromium/issues/detail?id=162042
+// TODO(bryeung): delete this file.
+binding.registerCustomHook(function(api) {
+ var apiFunctions = api.apiFunctions;
+ var permissions = api.compiledApi;
+
+ function maybeConvertToObject(str) {
+ var parts = $String.split(str, '|');
+ if (parts.length != 2)
+ return str;
+
+ var ret = {};
+ ret[parts[0]] = JSON.parse(parts[1]);
+ return ret;
+ }
+
+ function convertObjectPermissionsToStrings() {
+ if (arguments.length < 1)
+ return arguments;
+
+ var args = arguments[0].permissions;
+ if (!args)
+ return arguments;
+
+ for (var i = 0; i < args.length; i += 1) {
+ if (typeof(args[i]) == 'object') {
+ var a = args[i];
+ var keys = $Object.keys(a);
+ if (keys.length != 1) {
+ throw new Error("Too many keys in object-style permission.");
+ }
+ arguments[0].permissions[i] = keys[0] + '|' +
+ JSON.stringify(a[keys[0]]);
+ }
+ }
+
+ return arguments;
+ }
+
+ // Convert complex permissions to strings so they validate against the schema
+ apiFunctions.setUpdateArgumentsPreValidate(
+ 'contains', convertObjectPermissionsToStrings);
+ apiFunctions.setUpdateArgumentsPreValidate(
+ 'remove', convertObjectPermissionsToStrings);
+ apiFunctions.setUpdateArgumentsPreValidate(
+ 'request', convertObjectPermissionsToStrings);
+
+ // Convert complex permissions back to objects
+ apiFunctions.setCustomCallback('getAll',
+ function(name, request, callback, response) {
+ for (var i = 0; i < response.permissions.length; i += 1) {
+ response.permissions[i] =
+ maybeConvertToObject(response.permissions[i]);
+ }
+
+ // Since the schema says Permissions.permissions contains strings and
+ // not objects, validation will fail after the for-loop above. This
+ // skips validation and calls the callback directly.
+ if (callback)
+ callback(response);
+ });
+
+ // Also convert complex permissions back to objects for events. The
+ // dispatchToListener call happens after argument validation, which works
+ // around the problem that Permissions.permissions is supposed to be a list
+ // of strings.
+ permissions.onAdded.dispatchToListener = function(callback, args) {
+ for (var i = 0; i < args[0].permissions.length; i += 1) {
+ args[0].permissions[i] = maybeConvertToObject(args[0].permissions[i]);
+ }
+ $Function.call(Event.prototype.dispatchToListener, this, callback, args);
+ };
+ permissions.onRemoved.dispatchToListener =
+ permissions.onAdded.dispatchToListener;
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/platform_app.css b/chromium/extensions/renderer/resources/platform_app.css
new file mode 100644
index 00000000000..fa811310bd9
--- /dev/null
+++ b/chromium/extensions/renderer/resources/platform_app.css
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014 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.
+ *
+ * A style sheet for Chrome apps.
+ */
+
+@namespace "http://www.w3.org/1999/xhtml";
+
+body {
+ -webkit-user-select: none;
+ cursor: default;
+ font-family: $FONTFAMILY;
+ font-size: $FONTSIZE;
+}
+
+webview, appview {
+ display: inline-block;
+ width: 300px;
+ height: 300px;
+}
+
+html, body {
+ overflow: hidden;
+}
+
+img, a {
+ -webkit-user-drag: none;
+}
+
+[contenteditable], input {
+ -webkit-user-select: auto;
+}
+
diff --git a/chromium/extensions/renderer/resources/platform_app.js b/chromium/extensions/renderer/resources/platform_app.js
new file mode 100644
index 00000000000..9f386d790d2
--- /dev/null
+++ b/chromium/extensions/renderer/resources/platform_app.js
@@ -0,0 +1,232 @@
+// Copyright 2014 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.
+
+var logging = requireNative('logging');
+
+/**
+ * Returns a function that logs a 'not available' error to the console and
+ * returns undefined.
+ *
+ * @param {string} messagePrefix text to prepend to the exception message.
+ */
+function generateDisabledMethodStub(messagePrefix, opt_messageSuffix) {
+ var message = messagePrefix + ' is not available in packaged apps.';
+ if (opt_messageSuffix) message = message + ' ' + opt_messageSuffix;
+ return function() {
+ console.error(message);
+ return;
+ };
+}
+
+/**
+ * Returns a function that throws a 'not available' error.
+ *
+ * @param {string} messagePrefix text to prepend to the exception message.
+ */
+function generateThrowingMethodStub(messagePrefix, opt_messageSuffix) {
+ var message = messagePrefix + ' is not available in packaged apps.';
+ if (opt_messageSuffix) message = message + ' ' + opt_messageSuffix;
+ return function() {
+ throw new Error(message);
+ };
+}
+
+/**
+ * Replaces the given methods of the passed in object with stubs that log
+ * 'not available' errors to the console and return undefined.
+ *
+ * This should be used on methods attached via non-configurable properties,
+ * such as window.alert. disableGetters should be used when possible, because
+ * it is friendlier towards feature detection.
+ *
+ * In most cases, the useThrowingStubs should be false, so the stubs used to
+ * replace the methods log an error to the console, but allow the calling code
+ * to continue. We shouldn't break library code that uses feature detection
+ * responsibly, such as:
+ * if(window.confirm) {
+ * var result = window.confirm('Are you sure you want to delete ...?');
+ * ...
+ * }
+ *
+ * useThrowingStubs should only be true for methods that are deprecated in the
+ * Web platform, and should not be used by a responsible library, even in
+ * conjunction with feature detection. A great example is document.write(), as
+ * the HTML5 specification recommends against using it, and says that its
+ * behavior is unreliable. No reasonable library code should ever use it.
+ * HTML5 spec: http://www.w3.org/TR/html5/dom.html#dom-document-write
+ *
+ * @param {Object} object The object with methods to disable. The prototype is
+ * preferred.
+ * @param {string} objectName The display name to use in the error message
+ * thrown by the stub (this is the name that the object is commonly referred
+ * to by web developers, e.g. "document" instead of "HTMLDocument").
+ * @param {Array<string>} methodNames names of methods to disable.
+ * @param {Boolean} useThrowingStubs if true, the replaced methods will throw
+ * an error instead of silently returning undefined
+ */
+function disableMethods(object, objectName, methodNames, useThrowingStubs) {
+ $Array.forEach(methodNames, function(methodName) {
+ logging.DCHECK($Object.getOwnPropertyDescriptor(object, methodName),
+ objectName + ': ' + methodName);
+ var messagePrefix = objectName + '.' + methodName + '()';
+ $Object.defineProperty(object, methodName, {
+ configurable: false,
+ enumerable: false,
+ value: useThrowingStubs ?
+ generateThrowingMethodStub(messagePrefix) :
+ generateDisabledMethodStub(messagePrefix)
+ });
+ });
+}
+
+/**
+ * Replaces the given properties of the passed in object with stubs that log
+ * 'not available' warnings to the console and return undefined when gotten. If
+ * a property's setter is later invoked, the getter and setter are restored to
+ * default behaviors.
+ *
+ * @param {Object} object The object with properties to disable. The prototype
+ * is preferred.
+ * @param {string} objectName The display name to use in the error message
+ * thrown by the getter stub (this is the name that the object is commonly
+ * referred to by web developers, e.g. "document" instead of
+ * "HTMLDocument").
+ * @param {Array<string>} propertyNames names of properties to disable.
+ * @param {?string=} opt_messageSuffix An optional suffix for the message.
+ * @param {boolean=} opt_ignoreMissingProperty True if we allow disabling
+ * getters for non-existent properties.
+ */
+function disableGetters(object, objectName, propertyNames, opt_messageSuffix,
+ opt_ignoreMissingProperty) {
+ $Array.forEach(propertyNames, function(propertyName) {
+ logging.DCHECK(opt_ignoreMissingProperty ||
+ $Object.getOwnPropertyDescriptor(object, propertyName),
+ objectName + ': ' + propertyName);
+ var stub = generateDisabledMethodStub(objectName + '.' + propertyName,
+ opt_messageSuffix);
+ stub._is_platform_app_disabled_getter = true;
+ $Object.defineProperty(object, propertyName, {
+ configurable: true,
+ enumerable: false,
+ get: stub,
+ set: function(value) {
+ var descriptor = $Object.getOwnPropertyDescriptor(this, propertyName);
+ if (!descriptor || !descriptor.get ||
+ descriptor.get._is_platform_app_disabled_getter) {
+ // The stub getter is still defined. Blow-away the property to
+ // restore default getter/setter behaviors and re-create it with the
+ // given value.
+ delete this[propertyName];
+ this[propertyName] = value;
+ } else {
+ // Do nothing. If some custom getter (not ours) has been defined,
+ // there would be no way to read back the value stored by a default
+ // setter. Also, the only way to clear a custom getter is to first
+ // delete the property. Therefore, the value we have here should
+ // just go into a black hole.
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Replaces the given properties of the passed in object with stubs that log
+ * 'not available' warnings to the console when set.
+ *
+ * @param {Object} object The object with properties to disable. The prototype
+ * is preferred.
+ * @param {string} objectName The display name to use in the error message
+ * thrown by the setter stub (this is the name that the object is commonly
+ * referred to by web developers, e.g. "document" instead of
+ * "HTMLDocument").
+ * @param {Array<string>} propertyNames names of properties to disable.
+ */
+function disableSetters(object, objectName, propertyNames, opt_messageSuffix) {
+ $Array.forEach(propertyNames, function(propertyName) {
+ logging.DCHECK($Object.getOwnPropertyDescriptor(object, propertyName),
+ objectName + ': ' + propertyName);
+ var stub = generateDisabledMethodStub(objectName + '.' + propertyName,
+ opt_messageSuffix);
+ $Object.defineProperty(object, propertyName, {
+ configurable: false,
+ enumerable: false,
+ get: function() {
+ return;
+ },
+ set: stub
+ });
+ });
+}
+
+// Disable benign Document methods.
+disableMethods(Document.prototype, 'document', ['open', 'close']);
+disableMethods(HTMLDocument.prototype, 'document', ['clear']);
+
+// Replace evil Document methods with exception-throwing stubs.
+disableMethods(Document.prototype, 'document', ['write', 'writeln'], true);
+
+// Disable history.
+Object.defineProperty(window, "history", { value: {} });
+// Note: we just blew away the history object, so we need to ignore the fact
+// that these properties aren't defined on the object.
+disableGetters(window.history, 'history',
+ ['back', 'forward', 'go', 'length', 'pushState', 'replaceState', 'state'],
+ null, true);
+
+// Disable find.
+disableMethods(window, 'window', ['find']);
+
+// Disable modal dialogs. Shell windows disable these anyway, but it's nice to
+// warn.
+disableMethods(window, 'window', ['alert', 'confirm', 'prompt']);
+
+// Disable window.*bar.
+disableGetters(window, 'window',
+ ['locationbar', 'menubar', 'personalbar', 'scrollbars', 'statusbar',
+ 'toolbar']);
+
+// Disable window.localStorage.
+// Sometimes DOM security policy prevents us from doing this (e.g. for data:
+// URLs) so wrap in try-catch.
+try {
+ disableGetters(window, 'window',
+ ['localStorage'],
+ 'Use chrome.storage.local instead.');
+} catch (e) {}
+
+// Document instance properties that we wish to disable need to be set when
+// the document begins loading, since only then will the "document" reference
+// point to the page's document (it will be reset between now and then).
+// We can't listen for the "readystatechange" event on the document (because
+// the object that it's dispatched on doesn't exist yet), but we can instead
+// do it at the window level in the capturing phase.
+window.addEventListener('readystatechange', function(event) {
+ if (document.readyState != 'loading')
+ return;
+
+ // Deprecated document properties from
+ // https://developer.mozilla.org/en/DOM/document.
+ // To deprecate document.all, simply changing its getter and setter would
+ // activate its cache mechanism, and degrade the performance. Here we assign
+ // it first to 'undefined' to avoid this.
+ document.all = undefined;
+ disableGetters(document, 'document',
+ ['alinkColor', 'all', 'bgColor', 'fgColor', 'linkColor', 'vlinkColor'],
+ null, true);
+}, true);
+
+// Disable onunload, onbeforeunload.
+disableSetters(window, 'window', ['onbeforeunload', 'onunload']);
+var eventTargetAddEventListener = EventTarget.prototype.addEventListener;
+EventTarget.prototype.addEventListener = function(type) {
+ var args = $Array.slice(arguments);
+ // Note: Force conversion to a string in order to catch any funny attempts
+ // to pass in something that evals to 'unload' but wouldn't === 'unload'.
+ var type = (args[0] += '');
+ if (type === 'unload' || type === 'beforeunload')
+ generateDisabledMethodStub(type)();
+ else
+ return $Function.apply(eventTargetAddEventListener, this, args);
+};
diff --git a/chromium/extensions/renderer/resources/printer_provider_custom_bindings.js b/chromium/extensions/renderer/resources/printer_provider_custom_bindings.js
new file mode 100644
index 00000000000..4bf6f12f760
--- /dev/null
+++ b/chromium/extensions/renderer/resources/printer_provider_custom_bindings.js
@@ -0,0 +1,123 @@
+// Copyright 2015 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.
+
+var binding = require('binding').Binding.create('printerProvider');
+var printerProviderInternal = require('binding').Binding.create(
+ 'printerProviderInternal').generate();
+var eventBindings = require('event_bindings');
+var blobNatives = requireNative('blob_natives');
+
+var printerProviderSchema =
+ requireNative('schema_registry').GetSchema('printerProvider')
+var utils = require('utils');
+var validate = require('schemaUtils').validate;
+
+// Custom bindings for chrome.printerProvider API.
+// The bindings are used to implement callbacks for the API events. Internally
+// each event is passed requestId argument used to identify the callback
+// associated with the event. This argument is massaged out from the event
+// arguments before dispatching the event to consumers. A callback is appended
+// to the event arguments. The callback wraps an appropriate
+// chrome.printerProviderInternal API function that is used to report the event
+// result from the extension. The function is passed requestId and values
+// provided by the extension. It validates that the values provided by the
+// extension match chrome.printerProvider event callback schemas. It also
+// ensures that a callback is run at most once. In case there is an exception
+// during event dispatching, the chrome.printerProviderInternal function
+// is called with a default error value.
+//
+
+// Handles a chrome.printerProvider event as described in the file comment.
+// |eventName|: The event name.
+// |prepareArgsForDispatch|: Function called before dispatching the event to
+// the extension. It's called with original event |args| list and callback
+// that should be called when the |args| are ready for dispatch. The
+// callbacks should report whether the argument preparation was successful.
+// The function should not change the first argument, which contains the
+// request id.
+// |resultreporter|: The function that should be called to report event result.
+// One of chrome.printerProviderInternal API functions.
+function handleEvent(eventName, prepareArgsForDispatch, resultReporter) {
+ eventBindings.registerArgumentMassager(
+ 'printerProvider.' + eventName,
+ function(args, dispatch) {
+ var responded = false;
+
+ // Validates that the result passed by the extension to the event
+ // callback matches the callback schema. Throws an exception in case of
+ // an error.
+ var validateResult = function(result) {
+ var eventSchema =
+ utils.lookup(printerProviderSchema.events, 'name', eventName);
+ var callbackSchema =
+ utils.lookup(eventSchema.parameters, 'type', 'function');
+
+ validate([result], callbackSchema.parameters);
+ };
+
+ // Function provided to the extension as the event callback argument.
+ // It makes sure that the event result hasn't previously been returned
+ // and that the provided result matches the callback schema. In case of
+ // an error it throws an exception.
+ var reportResult = function(result) {
+ if (responded) {
+ throw new Error(
+ 'Event callback must not be called more than once.');
+ }
+
+ var finalResult = null;
+ try {
+ validateResult(result); // throws on failure
+ finalResult = result;
+ } finally {
+ responded = true;
+ resultReporter(args[0] /* requestId */, finalResult);
+ }
+ };
+
+ prepareArgsForDispatch(args, function(success) {
+ if (!success) {
+ // Do not throw an exception since the extension should not yet be
+ // aware of the event.
+ resultReporter(args[0] /* requestId */, null);
+ return;
+ }
+ dispatch(args.slice(1).concat(reportResult));
+ });
+ });
+}
+
+// Sets up printJob.document property for a print request.
+function createPrintRequestBlobArguments(args, callback) {
+ printerProviderInternal.getPrintData(args[0] /* requestId */,
+ function(blobInfo) {
+ if (chrome.runtime.lastError) {
+ callback(false);
+ return;
+ }
+
+ // |args[1]| is printJob.
+ args[1].document = blobNatives.TakeBrowserProcessBlob(
+ blobInfo.blobUuid, blobInfo.type, blobInfo.size);
+ callback(true);
+ });
+}
+
+handleEvent('onGetPrintersRequested',
+ function(args, callback) { callback(true); },
+ printerProviderInternal.reportPrinters);
+
+handleEvent('onGetCapabilityRequested',
+ function(args, callback) { callback(true); },
+ printerProviderInternal.reportPrinterCapability);
+
+handleEvent('onPrintRequested',
+ createPrintRequestBlobArguments,
+ printerProviderInternal.reportPrintResult);
+
+handleEvent('onGetUsbPrinterInfoRequested',
+ function(args, callback) { callback(true); },
+ printerProviderInternal.reportUsbPrinterInfo);
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/runtime_custom_bindings.js b/chromium/extensions/renderer/resources/runtime_custom_bindings.js
new file mode 100644
index 00000000000..1f829d4deb1
--- /dev/null
+++ b/chromium/extensions/renderer/resources/runtime_custom_bindings.js
@@ -0,0 +1,206 @@
+// Copyright 2014 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.
+
+// Custom binding for the runtime API.
+
+var binding = require('binding').Binding.create('runtime');
+
+var messaging = require('messaging');
+var runtimeNatives = requireNative('runtime');
+var process = requireNative('process');
+var forEach = require('utils').forEach;
+
+var backgroundPage = window;
+var backgroundRequire = require;
+var contextType = process.GetContextType();
+if (contextType == 'BLESSED_EXTENSION' ||
+ contextType == 'UNBLESSED_EXTENSION') {
+ var manifest = runtimeNatives.GetManifest();
+ if (manifest.app && manifest.app.background) {
+ // Get the background page if one exists. Otherwise, default to the current
+ // window.
+ backgroundPage = runtimeNatives.GetExtensionViews(-1, 'BACKGROUND')[0];
+ if (backgroundPage) {
+ var GetModuleSystem = requireNative('v8_context').GetModuleSystem;
+ backgroundRequire = GetModuleSystem(backgroundPage).require;
+ } else {
+ backgroundPage = window;
+ }
+ }
+}
+
+// For packaged apps, all windows use the bindFileEntryCallback from the
+// background page so their FileEntry objects have the background page's context
+// as their own. This allows them to be used from other windows (including the
+// background page) after the original window is closed.
+if (window == backgroundPage) {
+ var lastError = require('lastError');
+ var fileSystemNatives = requireNative('file_system_natives');
+ var GetIsolatedFileSystem = fileSystemNatives.GetIsolatedFileSystem;
+ var bindDirectoryEntryCallback = function(functionName, apiFunctions) {
+ apiFunctions.setCustomCallback(functionName,
+ function(name, request, callback, response) {
+ if (callback) {
+ if (!response) {
+ callback();
+ return;
+ }
+ var fileSystemId = response.fileSystemId;
+ var baseName = response.baseName;
+ var fs = GetIsolatedFileSystem(fileSystemId);
+
+ try {
+ fs.root.getDirectory(baseName, {}, callback, function(fileError) {
+ lastError.run('runtime.' + functionName,
+ 'Error getting Entry, code: ' + fileError.code,
+ request.stack,
+ callback);
+ });
+ } catch (e) {
+ lastError.run('runtime.' + functionName,
+ 'Error: ' + e.stack,
+ request.stack,
+ callback);
+ }
+ }
+ });
+ };
+} else {
+ // Force the runtime API to be loaded in the background page. Using
+ // backgroundPageModuleSystem.require('runtime') is insufficient as
+ // requireNative is only allowed while lazily loading an API.
+ backgroundPage.chrome.runtime;
+ var bindDirectoryEntryCallback = backgroundRequire(
+ 'runtime').bindDirectoryEntryCallback;
+}
+
+binding.registerCustomHook(function(binding, id, contextType) {
+ var apiFunctions = binding.apiFunctions;
+ var runtime = binding.compiledApi;
+
+ //
+ // Unprivileged APIs.
+ //
+
+ if (id != '')
+ runtime.id = id;
+
+ apiFunctions.setHandleRequest('getManifest', function() {
+ return runtimeNatives.GetManifest();
+ });
+
+ apiFunctions.setHandleRequest('getURL', function(path) {
+ path = String(path);
+ if (!path.length || path[0] != '/')
+ path = '/' + path;
+ return 'chrome-extension://' + id + path;
+ });
+
+ var sendMessageUpdateArguments = messaging.sendMessageUpdateArguments;
+ apiFunctions.setUpdateArgumentsPreValidate('sendMessage',
+ $Function.bind(sendMessageUpdateArguments, null, 'sendMessage',
+ true /* hasOptionsArgument */));
+ apiFunctions.setUpdateArgumentsPreValidate('sendNativeMessage',
+ $Function.bind(sendMessageUpdateArguments, null, 'sendNativeMessage',
+ false /* hasOptionsArgument */));
+
+ apiFunctions.setHandleRequest('sendMessage',
+ function(targetId, message, options, responseCallback) {
+ var connectOptions = {name: messaging.kMessageChannel};
+ forEach(options, function(k, v) {
+ connectOptions[k] = v;
+ });
+ var port = runtime.connect(targetId || runtime.id, connectOptions);
+ messaging.sendMessageImpl(port, message, responseCallback);
+ });
+
+ apiFunctions.setHandleRequest('sendNativeMessage',
+ function(targetId, message, responseCallback) {
+ var port = runtime.connectNative(targetId);
+ messaging.sendMessageImpl(port, message, responseCallback);
+ });
+
+ apiFunctions.setUpdateArgumentsPreValidate('connect', function() {
+ // Align missing (optional) function arguments with the arguments that
+ // schema validation is expecting, e.g.
+ // runtime.connect() -> runtime.connect(null, null)
+ // runtime.connect({}) -> runtime.connect(null, {})
+ var nextArg = 0;
+
+ // targetId (first argument) is optional.
+ var targetId = null;
+ if (typeof(arguments[nextArg]) == 'string')
+ targetId = arguments[nextArg++];
+
+ // connectInfo (second argument) is optional.
+ var connectInfo = null;
+ if (typeof(arguments[nextArg]) == 'object')
+ connectInfo = arguments[nextArg++];
+
+ if (nextArg != arguments.length)
+ throw new Error('Invalid arguments to connect.');
+ return [targetId, connectInfo];
+ });
+
+ apiFunctions.setUpdateArgumentsPreValidate('connectNative',
+ function(appName) {
+ if (typeof(appName) !== 'string') {
+ throw new Error('Invalid arguments to connectNative.');
+ }
+ return [appName];
+ });
+
+ apiFunctions.setHandleRequest('connect', function(targetId, connectInfo) {
+ if (!targetId) {
+ // runtime.id is only defined inside extensions. If we're in a webpage,
+ // the best we can do at this point is to fail.
+ if (!runtime.id) {
+ throw new Error('chrome.runtime.connect() called from a webpage must ' +
+ 'specify an Extension ID (string) for its first ' +
+ 'argument');
+ }
+ targetId = runtime.id;
+ }
+
+ var name = '';
+ if (connectInfo && connectInfo.name)
+ name = connectInfo.name;
+
+ var includeTlsChannelId =
+ !!(connectInfo && connectInfo.includeTlsChannelId);
+
+ var portId = runtimeNatives.OpenChannelToExtension(targetId, name,
+ includeTlsChannelId);
+ if (portId >= 0)
+ return messaging.createPort(portId, name);
+ });
+
+ //
+ // Privileged APIs.
+ //
+ if (contextType != 'BLESSED_EXTENSION')
+ return;
+
+ apiFunctions.setHandleRequest('connectNative',
+ function(nativeAppName) {
+ var portId = runtimeNatives.OpenChannelToNativeApp(runtime.id,
+ nativeAppName);
+ if (portId >= 0)
+ return messaging.createPort(portId, '');
+ throw new Error('Error connecting to native app: ' + nativeAppName);
+ });
+
+ apiFunctions.setCustomCallback('getBackgroundPage',
+ function(name, request, callback, response) {
+ if (callback) {
+ var bg = runtimeNatives.GetExtensionViews(-1, 'BACKGROUND')[0] || null;
+ callback(bg);
+ }
+ });
+
+ bindDirectoryEntryCallback('getPackageDirectoryEntry', apiFunctions);
+});
+
+exports.$set('bindDirectoryEntryCallback', bindDirectoryEntryCallback);
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/schema_utils.js b/chromium/extensions/renderer/resources/schema_utils.js
new file mode 100644
index 00000000000..b14d2eb548d
--- /dev/null
+++ b/chromium/extensions/renderer/resources/schema_utils.js
@@ -0,0 +1,159 @@
+// Copyright 2014 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.
+
+// Routines used to validate and normalize arguments.
+// TODO(benwells): unit test this file.
+
+var JSONSchemaValidator = require('json_schema').JSONSchemaValidator;
+
+var schemaValidator = new JSONSchemaValidator();
+
+// Validate arguments.
+function validate(args, parameterSchemas) {
+ if (args.length > parameterSchemas.length)
+ throw new Error("Too many arguments.");
+ for (var i = 0; i < parameterSchemas.length; i++) {
+ if (i in args && args[i] !== null && args[i] !== undefined) {
+ schemaValidator.resetErrors();
+ schemaValidator.validate(args[i], parameterSchemas[i]);
+ if (schemaValidator.errors.length == 0)
+ continue;
+ var message = "Invalid value for argument " + (i + 1) + ". ";
+ for (var i = 0, err;
+ err = schemaValidator.errors[i]; i++) {
+ if (err.path) {
+ message += "Property '" + err.path + "': ";
+ }
+ message += err.message;
+ message = message.substring(0, message.length - 1);
+ message += ", ";
+ }
+ message = message.substring(0, message.length - 2);
+ message += ".";
+ throw new Error(message);
+ } else if (!parameterSchemas[i].optional) {
+ throw new Error("Parameter " + (i + 1) + " (" +
+ parameterSchemas[i].name + ") is required.");
+ }
+ }
+}
+
+// Generate all possible signatures for a given API function.
+function getSignatures(parameterSchemas) {
+ if (parameterSchemas.length === 0)
+ return [[]];
+ var signatures = [];
+ var remaining = getSignatures($Array.slice(parameterSchemas, 1));
+ for (var i = 0; i < remaining.length; i++)
+ $Array.push(signatures, $Array.concat([parameterSchemas[0]], remaining[i]))
+ if (parameterSchemas[0].optional)
+ return $Array.concat(signatures, remaining);
+ return signatures;
+};
+
+// Return true if arguments match a given signature's schema.
+function argumentsMatchSignature(args, candidateSignature) {
+ if (args.length != candidateSignature.length)
+ return false;
+ for (var i = 0; i < candidateSignature.length; i++) {
+ var argType = JSONSchemaValidator.getType(args[i]);
+ if (!schemaValidator.isValidSchemaType(argType,
+ candidateSignature[i]))
+ return false;
+ }
+ return true;
+};
+
+// Finds the function signature for the given arguments.
+function resolveSignature(args, definedSignature) {
+ var candidateSignatures = getSignatures(definedSignature);
+ for (var i = 0; i < candidateSignatures.length; i++) {
+ if (argumentsMatchSignature(args, candidateSignatures[i]))
+ return candidateSignatures[i];
+ }
+ return null;
+};
+
+// Returns a string representing the defined signature of the API function.
+// Example return value for chrome.windows.getCurrent:
+// "windows.getCurrent(optional object populate, function callback)"
+function getParameterSignatureString(name, definedSignature) {
+ var getSchemaTypeString = function(schema) {
+ var schemaTypes = schemaValidator.getAllTypesForSchema(schema);
+ var typeName = schemaTypes.join(" or ") + " " + schema.name;
+ if (schema.optional)
+ return "optional " + typeName;
+ return typeName;
+ };
+ var typeNames = $Array.map(definedSignature, getSchemaTypeString);
+ return name + "(" + typeNames.join(", ") + ")";
+};
+
+// Returns a string representing a call to an API function.
+// Example return value for call: chrome.windows.get(1, callback) is:
+// "windows.get(int, function)"
+function getArgumentSignatureString(name, args) {
+ var typeNames = $Array.map(args, JSONSchemaValidator.getType);
+ return name + "(" + typeNames.join(", ") + ")";
+};
+
+// Finds the correct signature for the given arguments, then validates the
+// arguments against that signature. Returns a 'normalized' arguments list
+// where nulls are inserted where optional parameters were omitted.
+// |args| is expected to be an array.
+function normalizeArgumentsAndValidate(args, funDef) {
+ if (funDef.allowAmbiguousOptionalArguments) {
+ validate(args, funDef.definition.parameters);
+ return args;
+ }
+ var definedSignature = funDef.definition.parameters;
+ var resolvedSignature = resolveSignature(args, definedSignature);
+ if (!resolvedSignature)
+ throw new Error("Invocation of form " +
+ getArgumentSignatureString(funDef.name, args) +
+ " doesn't match definition " +
+ getParameterSignatureString(funDef.name, definedSignature));
+ validate(args, resolvedSignature);
+ var normalizedArgs = [];
+ var ai = 0;
+ for (var si = 0; si < definedSignature.length; si++) {
+ // Handle integer -0 as 0.
+ if (JSONSchemaValidator.getType(args[ai]) === "integer" && args[ai] === 0)
+ args[ai] = 0;
+ if (definedSignature[si] === resolvedSignature[ai])
+ $Array.push(normalizedArgs, args[ai++]);
+ else
+ $Array.push(normalizedArgs, null);
+ }
+ return normalizedArgs;
+};
+
+// Validates that a given schema for an API function is not ambiguous.
+function isFunctionSignatureAmbiguous(functionDef) {
+ if (functionDef.allowAmbiguousOptionalArguments)
+ return false;
+ var signaturesAmbiguous = function(signature1, signature2) {
+ if (signature1.length != signature2.length)
+ return false;
+ for (var i = 0; i < signature1.length; i++) {
+ if (!schemaValidator.checkSchemaOverlap(
+ signature1[i], signature2[i]))
+ return false;
+ }
+ return true;
+ };
+ var candidateSignatures = getSignatures(functionDef.parameters);
+ for (var i = 0; i < candidateSignatures.length; i++) {
+ for (var j = i + 1; j < candidateSignatures.length; j++) {
+ if (signaturesAmbiguous(candidateSignatures[i], candidateSignatures[j]))
+ return true;
+ }
+ }
+ return false;
+};
+
+exports.$set('isFunctionSignatureAmbiguous', isFunctionSignatureAmbiguous);
+exports.$set('normalizeArgumentsAndValidate', normalizeArgumentsAndValidate);
+exports.$set('schemaValidator', schemaValidator);
+exports.$set('validate', validate);
diff --git a/chromium/extensions/renderer/resources/send_request.js b/chromium/extensions/renderer/resources/send_request.js
new file mode 100644
index 00000000000..9fed10e7f42
--- /dev/null
+++ b/chromium/extensions/renderer/resources/send_request.js
@@ -0,0 +1,151 @@
+// Copyright 2014 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.
+
+var exceptionHandler = require('uncaught_exception_handler');
+var lastError = require('lastError');
+var logging = requireNative('logging');
+var natives = requireNative('sendRequest');
+var validate = require('schemaUtils').validate;
+
+// All outstanding requests from sendRequest().
+var requests = {};
+
+// Used to prevent double Activity Logging for API calls that use both custom
+// bindings and ExtensionFunctions (via sendRequest).
+var calledSendRequest = false;
+
+// Runs a user-supplied callback safely.
+function safeCallbackApply(name, request, callback, args) {
+ try {
+ $Function.apply(callback, request, args);
+ } catch (e) {
+ exceptionHandler.handle('Error in response to ' + name, e, request.stack);
+ }
+}
+
+// Callback handling.
+function handleResponse(requestId, name, success, responseList, error) {
+ // The chrome objects we will set lastError on. Really we should only be
+ // setting this on the callback's chrome object, but set on ours too since
+ // it's conceivable that something relies on that.
+ var callerChrome = chrome;
+
+ try {
+ var request = requests[requestId];
+ logging.DCHECK(request != null);
+
+ // lastError needs to be set on the caller's chrome object no matter what,
+ // though chances are it's the same as ours (it will be different when
+ // calling API methods on other contexts).
+ if (request.callback) {
+ var global = natives.GetGlobal(request.callback);
+ callerChrome = global ? global.chrome : callerChrome;
+ }
+
+ lastError.clear(chrome);
+ if (callerChrome !== chrome)
+ lastError.clear(callerChrome);
+
+ if (!success) {
+ if (!error)
+ error = "Unknown error.";
+ lastError.set(name, error, request.stack, chrome);
+ if (callerChrome !== chrome)
+ lastError.set(name, error, request.stack, callerChrome);
+ }
+
+ if (request.customCallback) {
+ safeCallbackApply(name,
+ request,
+ request.customCallback,
+ $Array.concat([name, request, request.callback],
+ responseList));
+ } else if (request.callback) {
+ // Validate callback in debug only -- and only when the
+ // caller has provided a callback. Implementations of api
+ // calls may not return data if they observe the caller
+ // has not provided a callback.
+ if (logging.DCHECK_IS_ON() && !error) {
+ if (!request.callbackSchema.parameters)
+ throw new Error(name + ": no callback schema defined");
+ validate(responseList, request.callbackSchema.parameters);
+ }
+ safeCallbackApply(name, request, request.callback, responseList);
+ }
+
+ if (error && !lastError.hasAccessed(chrome)) {
+ // The native call caused an error, but the developer might not have
+ // checked runtime.lastError.
+ lastError.reportIfUnchecked(name, callerChrome, request.stack);
+ }
+ } finally {
+ delete requests[requestId];
+ lastError.clear(chrome);
+ if (callerChrome !== chrome)
+ lastError.clear(callerChrome);
+ }
+}
+
+function prepareRequest(args, argSchemas) {
+ var request = {};
+ var argCount = args.length;
+
+ // Look for callback param.
+ if (argSchemas.length > 0 &&
+ argSchemas[argSchemas.length - 1].type == "function") {
+ request.callback = args[args.length - 1];
+ request.callbackSchema = argSchemas[argSchemas.length - 1];
+ --argCount;
+ }
+
+ request.args = [];
+ for (var k = 0; k < argCount; k++) {
+ request.args[k] = args[k];
+ }
+
+ return request;
+}
+
+// Send an API request and optionally register a callback.
+// |optArgs| is an object with optional parameters as follows:
+// - customCallback: a callback that should be called instead of the standard
+// callback.
+// - forIOThread: true if this function should be handled on the browser IO
+// thread.
+// - preserveNullInObjects: true if it is safe for null to be in objects.
+// - stack: An optional string that contains the stack trace, to be displayed
+// to the user if an error occurs.
+function sendRequest(functionName, args, argSchemas, optArgs) {
+ calledSendRequest = true;
+ if (!optArgs)
+ optArgs = {};
+ var request = prepareRequest(args, argSchemas);
+ request.stack = optArgs.stack || exceptionHandler.getExtensionStackTrace();
+ if (optArgs.customCallback) {
+ request.customCallback = optArgs.customCallback;
+ }
+
+ var hasCallback = request.callback || optArgs.customCallback;
+ var requestId =
+ natives.StartRequest(functionName, request.args, hasCallback,
+ optArgs.forIOThread, optArgs.preserveNullInObjects);
+ request.id = requestId;
+ requests[requestId] = request;
+}
+
+function getCalledSendRequest() {
+ return calledSendRequest;
+}
+
+function clearCalledSendRequest() {
+ calledSendRequest = false;
+}
+
+exports.$set('sendRequest', sendRequest);
+exports.$set('getCalledSendRequest', getCalledSendRequest);
+exports.$set('clearCalledSendRequest', clearCalledSendRequest);
+exports.$set('safeCallbackApply', safeCallbackApply);
+
+// Called by C++.
+exports.$set('handleResponse', handleResponse);
diff --git a/chromium/extensions/renderer/resources/serial_custom_bindings.js b/chromium/extensions/renderer/resources/serial_custom_bindings.js
new file mode 100644
index 00000000000..d77631cf645
--- /dev/null
+++ b/chromium/extensions/renderer/resources/serial_custom_bindings.js
@@ -0,0 +1,117 @@
+// Copyright 2014 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.
+
+/**
+ * Custom bindings for the Serial API.
+ *
+ * The bindings are implemented by asynchronously delegating to the
+ * serial_service module. The functions that apply to a particular connection
+ * are delegated to the appropriate method on the Connection object specified by
+ * the ID parameter.
+ */
+
+var binding = require('binding').Binding.create('serial');
+var context = requireNative('v8_context');
+var eventBindings = require('event_bindings');
+var utils = require('utils');
+
+var serialServicePromise = function() {
+ // getBackgroundPage is not available in unit tests so fall back to the
+ // current page's serial_service module.
+ if (!chrome.runtime.getBackgroundPage)
+ return requireAsync('serial_service');
+
+ // Load the serial_service module from the background page if one exists. This
+ // is necessary for serial connections created in one window to be usable
+ // after that window is closed. This is necessary because the Mojo async
+ // waiter only functions while the v8 context remains.
+ return utils.promise(chrome.runtime.getBackgroundPage).then(function(bgPage) {
+ return context.GetModuleSystem(bgPage).requireAsync('serial_service');
+ }).catch(function() {
+ return requireAsync('serial_service');
+ });
+}();
+
+function forwardToConnection(methodName) {
+ return function(connectionId) {
+ var args = $Array.slice(arguments, 1);
+ return serialServicePromise.then(function(serialService) {
+ return serialService.getConnection(connectionId);
+ }).then(function(connection) {
+ return $Function.apply(connection[methodName], connection, args);
+ });
+ };
+}
+
+function addEventListeners(connection, id) {
+ connection.onData = function(data) {
+ eventBindings.dispatchEvent(
+ 'serial.onReceive', [{connectionId: id, data: data}]);
+ };
+ connection.onError = function(error) {
+ eventBindings.dispatchEvent(
+ 'serial.onReceiveError', [{connectionId: id, error: error}]);
+ };
+}
+
+serialServicePromise.then(function(serialService) {
+ return serialService.getConnections().then(function(connections) {
+ for (var entry of connections) {
+ var connection = entry[1];
+ addEventListeners(connection, entry[0]);
+ connection.resumeReceives();
+ };
+ });
+});
+
+binding.registerCustomHook(function(bindingsAPI) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+ apiFunctions.setHandleRequestWithPromise('getDevices', function() {
+ return serialServicePromise.then(function(serialService) {
+ return serialService.getDevices();
+ });
+ });
+
+ apiFunctions.setHandleRequestWithPromise('connect', function(path, options) {
+ return serialServicePromise.then(function(serialService) {
+ return serialService.createConnection(path, options);
+ }).then(function(result) {
+ addEventListeners(result.connection, result.info.connectionId);
+ return result.info;
+ }).catch (function(e) {
+ throw new Error('Failed to connect to the port.');
+ });
+ });
+
+ apiFunctions.setHandleRequestWithPromise(
+ 'disconnect', forwardToConnection('close'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'getInfo', forwardToConnection('getInfo'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'update', forwardToConnection('setOptions'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'getControlSignals', forwardToConnection('getControlSignals'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'setControlSignals', forwardToConnection('setControlSignals'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'flush', forwardToConnection('flush'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'setPaused', forwardToConnection('setPaused'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'send', forwardToConnection('send'));
+
+ apiFunctions.setHandleRequestWithPromise('getConnections', function() {
+ return serialServicePromise.then(function(serialService) {
+ return serialService.getConnections();
+ }).then(function(connections) {
+ var promises = [];
+ for (var connection of connections.values()) {
+ promises.push(connection.getInfo());
+ }
+ return Promise.all(promises);
+ });
+ });
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/serial_service.js b/chromium/extensions/renderer/resources/serial_service.js
new file mode 100644
index 00000000000..26b990b227e
--- /dev/null
+++ b/chromium/extensions/renderer/resources/serial_service.js
@@ -0,0 +1,554 @@
+// Copyright 2014 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.
+
+define('serial_service', [
+ 'content/public/renderer/frame_service_registry',
+ 'data_receiver',
+ 'data_sender',
+ 'device/serial/serial.mojom',
+ 'device/serial/serial_serialization.mojom',
+ 'mojo/public/js/core',
+ 'mojo/public/js/router',
+ 'stash_client',
+], function(serviceProvider,
+ dataReceiver,
+ dataSender,
+ serialMojom,
+ serialization,
+ core,
+ routerModule,
+ stashClient) {
+ /**
+ * A Javascript client for the serial service and connection Mojo services.
+ *
+ * This provides a thick client around the Mojo services, exposing a JS-style
+ * interface to serial connections and information about serial devices. This
+ * converts parameters and result between the Apps serial API types and the
+ * Mojo types.
+ */
+
+ var service = new serialMojom.SerialService.proxyClass(
+ new routerModule.Router(
+ serviceProvider.connectToService(serialMojom.SerialService.name)));
+
+ function getDevices() {
+ return service.getDevices().then(function(response) {
+ return $Array.map(response.devices, function(device) {
+ var result = {path: device.path};
+ if (device.has_vendor_id)
+ result.vendorId = device.vendor_id;
+ if (device.has_product_id)
+ result.productId = device.product_id;
+ if (device.display_name)
+ result.displayName = device.display_name;
+ return result;
+ });
+ });
+ }
+
+ var DATA_BITS_TO_MOJO = {
+ undefined: serialMojom.DataBits.NONE,
+ 'seven': serialMojom.DataBits.SEVEN,
+ 'eight': serialMojom.DataBits.EIGHT,
+ };
+ var STOP_BITS_TO_MOJO = {
+ undefined: serialMojom.StopBits.NONE,
+ 'one': serialMojom.StopBits.ONE,
+ 'two': serialMojom.StopBits.TWO,
+ };
+ var PARITY_BIT_TO_MOJO = {
+ undefined: serialMojom.ParityBit.NONE,
+ 'no': serialMojom.ParityBit.NO,
+ 'odd': serialMojom.ParityBit.ODD,
+ 'even': serialMojom.ParityBit.EVEN,
+ };
+ var SEND_ERROR_TO_MOJO = {
+ undefined: serialMojom.SendError.NONE,
+ 'disconnected': serialMojom.SendError.DISCONNECTED,
+ 'pending': serialMojom.SendError.PENDING,
+ 'timeout': serialMojom.SendError.TIMEOUT,
+ 'system_error': serialMojom.SendError.SYSTEM_ERROR,
+ };
+ var RECEIVE_ERROR_TO_MOJO = {
+ undefined: serialMojom.ReceiveError.NONE,
+ 'disconnected': serialMojom.ReceiveError.DISCONNECTED,
+ 'device_lost': serialMojom.ReceiveError.DEVICE_LOST,
+ 'timeout': serialMojom.ReceiveError.TIMEOUT,
+ 'break': serialMojom.ReceiveError.BREAK,
+ 'frame_error': serialMojom.ReceiveError.FRAME_ERROR,
+ 'overrun': serialMojom.ReceiveError.OVERRUN,
+ 'buffer_overflow': serialMojom.ReceiveError.BUFFER_OVERFLOW,
+ 'parity_error': serialMojom.ReceiveError.PARITY_ERROR,
+ 'system_error': serialMojom.ReceiveError.SYSTEM_ERROR,
+ };
+
+ function invertMap(input) {
+ var output = {};
+ for (var key in input) {
+ if (key == 'undefined')
+ output[input[key]] = undefined;
+ else
+ output[input[key]] = key;
+ }
+ return output;
+ }
+ var DATA_BITS_FROM_MOJO = invertMap(DATA_BITS_TO_MOJO);
+ var STOP_BITS_FROM_MOJO = invertMap(STOP_BITS_TO_MOJO);
+ var PARITY_BIT_FROM_MOJO = invertMap(PARITY_BIT_TO_MOJO);
+ var SEND_ERROR_FROM_MOJO = invertMap(SEND_ERROR_TO_MOJO);
+ var RECEIVE_ERROR_FROM_MOJO = invertMap(RECEIVE_ERROR_TO_MOJO);
+
+ function getServiceOptions(options) {
+ var out = {};
+ if (options.dataBits)
+ out.data_bits = DATA_BITS_TO_MOJO[options.dataBits];
+ if (options.stopBits)
+ out.stop_bits = STOP_BITS_TO_MOJO[options.stopBits];
+ if (options.parityBit)
+ out.parity_bit = PARITY_BIT_TO_MOJO[options.parityBit];
+ if ('ctsFlowControl' in options) {
+ out.has_cts_flow_control = true;
+ out.cts_flow_control = options.ctsFlowControl;
+ }
+ if ('bitrate' in options)
+ out.bitrate = options.bitrate;
+ return out;
+ }
+
+ function convertServiceInfo(result) {
+ if (!result.info)
+ throw new Error('Failed to get ConnectionInfo.');
+ return {
+ ctsFlowControl: !!result.info.cts_flow_control,
+ bitrate: result.info.bitrate || undefined,
+ dataBits: DATA_BITS_FROM_MOJO[result.info.data_bits],
+ stopBits: STOP_BITS_FROM_MOJO[result.info.stop_bits],
+ parityBit: PARITY_BIT_FROM_MOJO[result.info.parity_bit],
+ };
+ }
+
+ // Update client-side options |clientOptions| from the user-provided
+ // |options|.
+ function updateClientOptions(clientOptions, options) {
+ if ('name' in options)
+ clientOptions.name = options.name;
+ if ('receiveTimeout' in options)
+ clientOptions.receiveTimeout = options.receiveTimeout;
+ if ('sendTimeout' in options)
+ clientOptions.sendTimeout = options.sendTimeout;
+ if ('bufferSize' in options)
+ clientOptions.bufferSize = options.bufferSize;
+ if ('persistent' in options)
+ clientOptions.persistent = options.persistent;
+ };
+
+ function Connection(connection, router, receivePipe, receiveClientPipe,
+ sendPipe, id, options) {
+ var state = new serialization.ConnectionState();
+ state.connectionId = id;
+ updateClientOptions(state, options);
+ var receiver = new dataReceiver.DataReceiver(
+ receivePipe, receiveClientPipe, state.bufferSize,
+ serialMojom.ReceiveError.DISCONNECTED);
+ var sender = new dataSender.DataSender(sendPipe, state.bufferSize,
+ serialMojom.SendError.DISCONNECTED);
+ this.init_(state,
+ connection,
+ router,
+ receiver,
+ sender,
+ null,
+ serialMojom.ReceiveError.NONE);
+ connections_.set(id, this);
+ this.startReceive_();
+ }
+
+ // Initializes this Connection from the provided args.
+ Connection.prototype.init_ = function(state,
+ connection,
+ router,
+ receiver,
+ sender,
+ queuedReceiveData,
+ queuedReceiveError) {
+ this.state_ = state;
+
+ // queuedReceiveData_ or queuedReceiveError_ will store the receive result
+ // or error, respectively, if a receive completes or fails while this
+ // connection is paused. At most one of the the two may be non-null: a
+ // receive completed while paused will only set one of them, no further
+ // receives will be performed while paused and a queued result is dispatched
+ // before any further receives are initiated when unpausing.
+ if (queuedReceiveError != serialMojom.ReceiveError.NONE)
+ this.queuedReceiveError_ = {error: queuedReceiveError};
+ if (queuedReceiveData) {
+ this.queuedReceiveData_ = new ArrayBuffer(queuedReceiveData.length);
+ new Int8Array(this.queuedReceiveData_).set(queuedReceiveData);
+ }
+ this.router_ = router;
+ this.remoteConnection_ = connection;
+ this.receivePipe_ = receiver;
+ this.sendPipe_ = sender;
+ this.sendInProgress_ = false;
+ };
+
+ Connection.create = function(path, options) {
+ options = options || {};
+ var serviceOptions = getServiceOptions(options);
+ var pipe = core.createMessagePipe();
+ var sendPipe = core.createMessagePipe();
+ var receivePipe = core.createMessagePipe();
+ var receivePipeClient = core.createMessagePipe();
+ service.connect(path,
+ serviceOptions,
+ pipe.handle0,
+ sendPipe.handle0,
+ receivePipe.handle0,
+ receivePipeClient.handle0);
+ var router = new routerModule.Router(pipe.handle1);
+ var connection = new serialMojom.Connection.proxyClass(router);
+ return connection.getInfo().then(convertServiceInfo).then(function(info) {
+ return Promise.all([info, allocateConnectionId()]);
+ }).catch(function(e) {
+ router.close();
+ core.close(sendPipe.handle1);
+ core.close(receivePipe.handle1);
+ core.close(receivePipeClient.handle1);
+ throw e;
+ }).then(function(results) {
+ var info = results[0];
+ var id = results[1];
+ var serialConnectionClient = new Connection(connection,
+ router,
+ receivePipe.handle1,
+ receivePipeClient.handle1,
+ sendPipe.handle1,
+ id,
+ options);
+ var clientInfo = serialConnectionClient.getClientInfo_();
+ for (var key in clientInfo) {
+ info[key] = clientInfo[key];
+ }
+ return {
+ connection: serialConnectionClient,
+ info: info,
+ };
+ });
+ };
+
+ Connection.prototype.close = function() {
+ this.router_.close();
+ this.receivePipe_.close();
+ this.sendPipe_.close();
+ clearTimeout(this.receiveTimeoutId_);
+ clearTimeout(this.sendTimeoutId_);
+ connections_.delete(this.state_.connectionId);
+ return true;
+ };
+
+ Connection.prototype.getClientInfo_ = function() {
+ return {
+ connectionId: this.state_.connectionId,
+ paused: this.state_.paused,
+ persistent: this.state_.persistent,
+ name: this.state_.name,
+ receiveTimeout: this.state_.receiveTimeout,
+ sendTimeout: this.state_.sendTimeout,
+ bufferSize: this.state_.bufferSize,
+ };
+ };
+
+ Connection.prototype.getInfo = function() {
+ var info = this.getClientInfo_();
+ return this.remoteConnection_.getInfo().then(convertServiceInfo).then(
+ function(result) {
+ for (var key in result) {
+ info[key] = result[key];
+ }
+ return info;
+ }).catch(function() {
+ return info;
+ });
+ };
+
+ Connection.prototype.setOptions = function(options) {
+ updateClientOptions(this.state_, options);
+ var serviceOptions = getServiceOptions(options);
+ if ($Object.keys(serviceOptions).length == 0)
+ return true;
+ return this.remoteConnection_.setOptions(serviceOptions).then(
+ function(result) {
+ return !!result.success;
+ }).catch(function() {
+ return false;
+ });
+ };
+
+ Connection.prototype.getControlSignals = function() {
+ return this.remoteConnection_.getControlSignals().then(function(result) {
+ if (!result.signals)
+ throw new Error('Failed to get control signals.');
+ var signals = result.signals;
+ return {
+ dcd: !!signals.dcd,
+ cts: !!signals.cts,
+ ri: !!signals.ri,
+ dsr: !!signals.dsr,
+ };
+ });
+ };
+
+ Connection.prototype.setControlSignals = function(signals) {
+ var controlSignals = {};
+ if ('dtr' in signals) {
+ controlSignals.has_dtr = true;
+ controlSignals.dtr = signals.dtr;
+ }
+ if ('rts' in signals) {
+ controlSignals.has_rts = true;
+ controlSignals.rts = signals.rts;
+ }
+ return this.remoteConnection_.setControlSignals(controlSignals).then(
+ function(result) {
+ return !!result.success;
+ });
+ };
+
+ Connection.prototype.flush = function() {
+ return this.remoteConnection_.flush().then(function(result) {
+ return !!result.success;
+ });
+ };
+
+ Connection.prototype.setPaused = function(paused) {
+ this.state_.paused = paused;
+ if (paused) {
+ clearTimeout(this.receiveTimeoutId_);
+ this.receiveTimeoutId_ = null;
+ } else if (!this.receiveInProgress_) {
+ this.startReceive_();
+ }
+ };
+
+ Connection.prototype.send = function(data) {
+ if (this.sendInProgress_)
+ return Promise.resolve({bytesSent: 0, error: 'pending'});
+
+ if (this.state_.sendTimeout) {
+ this.sendTimeoutId_ = setTimeout(function() {
+ this.sendPipe_.cancel(serialMojom.SendError.TIMEOUT);
+ }.bind(this), this.state_.sendTimeout);
+ }
+ this.sendInProgress_ = true;
+ return this.sendPipe_.send(data).then(function(bytesSent) {
+ return {bytesSent: bytesSent};
+ }).catch(function(e) {
+ return {
+ bytesSent: e.bytesSent,
+ error: SEND_ERROR_FROM_MOJO[e.error],
+ };
+ }).then(function(result) {
+ if (this.sendTimeoutId_)
+ clearTimeout(this.sendTimeoutId_);
+ this.sendTimeoutId_ = null;
+ this.sendInProgress_ = false;
+ return result;
+ }.bind(this));
+ };
+
+ Connection.prototype.startReceive_ = function() {
+ this.receiveInProgress_ = true;
+ var receivePromise = null;
+ // If we have a queued receive result, dispatch it immediately instead of
+ // starting a new receive.
+ if (this.queuedReceiveData_) {
+ receivePromise = Promise.resolve(this.queuedReceiveData_);
+ this.queuedReceiveData_ = null;
+ } else if (this.queuedReceiveError_) {
+ receivePromise = Promise.reject(this.queuedReceiveError_);
+ this.queuedReceiveError_ = null;
+ } else {
+ receivePromise = this.receivePipe_.receive();
+ }
+ receivePromise.then(this.onDataReceived_.bind(this)).catch(
+ this.onReceiveError_.bind(this));
+ this.startReceiveTimeoutTimer_();
+ };
+
+ Connection.prototype.onDataReceived_ = function(data) {
+ this.startReceiveTimeoutTimer_();
+ this.receiveInProgress_ = false;
+ if (this.state_.paused) {
+ this.queuedReceiveData_ = data;
+ return;
+ }
+ if (this.onData) {
+ this.onData(data);
+ }
+ if (!this.state_.paused) {
+ this.startReceive_();
+ }
+ };
+
+ Connection.prototype.onReceiveError_ = function(e) {
+ clearTimeout(this.receiveTimeoutId_);
+ this.receiveInProgress_ = false;
+ if (this.state_.paused) {
+ this.queuedReceiveError_ = e;
+ return;
+ }
+ var error = e.error;
+ this.state_.paused = true;
+ if (this.onError)
+ this.onError(RECEIVE_ERROR_FROM_MOJO[error]);
+ };
+
+ Connection.prototype.startReceiveTimeoutTimer_ = function() {
+ clearTimeout(this.receiveTimeoutId_);
+ if (this.state_.receiveTimeout && !this.state_.paused) {
+ this.receiveTimeoutId_ = setTimeout(this.onReceiveTimeout_.bind(this),
+ this.state_.receiveTimeout);
+ }
+ };
+
+ Connection.prototype.onReceiveTimeout_ = function() {
+ if (this.onError)
+ this.onError('timeout');
+ this.startReceiveTimeoutTimer_();
+ };
+
+ Connection.prototype.serialize = function() {
+ connections_.delete(this.state_.connectionId);
+ this.onData = null;
+ this.onError = null;
+ var handle = this.router_.connector_.handle_;
+ this.router_.connector_.handle_ = null;
+ this.router_.close();
+ clearTimeout(this.receiveTimeoutId_);
+ clearTimeout(this.sendTimeoutId_);
+
+ // Serializing receivePipe_ will cancel an in-progress receive, which would
+ // pause the connection, so save it ahead of time.
+ var paused = this.state_.paused;
+ return Promise.all([
+ this.receivePipe_.serialize(),
+ this.sendPipe_.serialize(),
+ ]).then(function(serializedComponents) {
+ var queuedReceiveError = serialMojom.ReceiveError.NONE;
+ if (this.queuedReceiveError_)
+ queuedReceiveError = this.queuedReceiveError_.error;
+ this.state_.paused = paused;
+ var serialized = new serialization.SerializedConnection();
+ serialized.state = this.state_;
+ serialized.queuedReceiveError = queuedReceiveError;
+ serialized.queuedReceiveData =
+ this.queuedReceiveData_ ? new Int8Array(this.queuedReceiveData_) :
+ null;
+ serialized.connection = handle;
+ serialized.receiver = serializedComponents[0];
+ serialized.sender = serializedComponents[1];
+ return serialized;
+ }.bind(this));
+ };
+
+ Connection.deserialize = function(serialized) {
+ var serialConnection = $Object.create(Connection.prototype);
+ var router = new routerModule.Router(serialized.connection);
+ var connection = new serialMojom.Connection.proxyClass(router);
+ var receiver = dataReceiver.DataReceiver.deserialize(serialized.receiver);
+ var sender = dataSender.DataSender.deserialize(serialized.sender);
+
+ // Ensure that paused and persistent are booleans.
+ serialized.state.paused = !!serialized.state.paused;
+ serialized.state.persistent = !!serialized.state.persistent;
+ serialConnection.init_(serialized.state,
+ connection,
+ router,
+ receiver,
+ sender,
+ serialized.queuedReceiveData,
+ serialized.queuedReceiveError);
+ serialConnection.awaitingResume_ = true;
+ var connectionId = serialized.state.connectionId;
+ connections_.set(connectionId, serialConnection);
+ if (connectionId >= nextConnectionId_)
+ nextConnectionId_ = connectionId + 1;
+ return serialConnection;
+ };
+
+ // Resume receives on a deserialized connection.
+ Connection.prototype.resumeReceives = function() {
+ if (!this.awaitingResume_)
+ return;
+ this.awaitingResume_ = false;
+ if (!this.state_.paused)
+ this.startReceive_();
+ };
+
+ // All accesses to connections_ and nextConnectionId_ other than those
+ // involved in deserialization should ensure that
+ // connectionDeserializationComplete_ has resolved first.
+ var connectionDeserializationComplete_ = stashClient.retrieve(
+ 'serial', serialization.SerializedConnection).then(function(decoded) {
+ if (!decoded)
+ return;
+ return Promise.all($Array.map(decoded, Connection.deserialize));
+ });
+
+ // The map of connection ID to connection object.
+ var connections_ = new Map();
+
+ // The next connection ID to be allocated.
+ var nextConnectionId_ = 0;
+
+ function getConnections() {
+ return connectionDeserializationComplete_.then(function() {
+ return new Map(connections_);
+ });
+ }
+
+ function getConnection(id) {
+ return getConnections().then(function(connections) {
+ if (!connections.has(id))
+ throw new Error('Serial connection not found.');
+ return connections.get(id);
+ });
+ }
+
+ function allocateConnectionId() {
+ return connectionDeserializationComplete_.then(function() {
+ return nextConnectionId_++;
+ });
+ }
+
+ stashClient.registerClient(
+ 'serial', serialization.SerializedConnection, function() {
+ return connectionDeserializationComplete_.then(function() {
+ var clientPromises = [];
+ for (var connection of connections_.values()) {
+ if (connection.state_.persistent)
+ clientPromises.push(connection.serialize());
+ else
+ connection.close();
+ }
+ return Promise.all($Array.map(clientPromises, function(promise) {
+ return promise.then(function(serialization) {
+ return {
+ serialization: serialization,
+ monitorHandles: !serialization.paused,
+ };
+ });
+ }));
+ });
+ });
+
+ return {
+ getDevices: getDevices,
+ createConnection: Connection.create,
+ getConnection: getConnection,
+ getConnections: getConnections,
+ // For testing.
+ Connection: Connection,
+ };
+});
diff --git a/chromium/extensions/renderer/resources/service_worker_bindings.js b/chromium/extensions/renderer/resources/service_worker_bindings.js
new file mode 100644
index 00000000000..532f51ddd2b
--- /dev/null
+++ b/chromium/extensions/renderer/resources/service_worker_bindings.js
@@ -0,0 +1,67 @@
+// Copyright 2015 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.
+
+// This function is returned to DidInitializeServiceWorkerContextOnWorkerThread
+// then executed, passing in dependencies as function arguments.
+//
+// |backgroundUrl| is the URL of the extension's background page.
+// |wakeEventPage| is a function that wakes up the current extension's event
+// page, then runs its callback on completion or failure.
+// |logging| is an object equivalent to a subset of base/debug/logging.h, with
+// CHECK/DCHECK/etc.
+(function(backgroundUrl, wakeEventPage, logging) {
+ 'use strict';
+ self.chrome = self.chrome || {};
+ self.chrome.runtime = self.chrome.runtime || {};
+
+ // Returns a Promise that resolves to the background page's client, or null
+ // if there is no background client.
+ function findBackgroundClient() {
+ return self.clients.matchAll({
+ includeUncontrolled: true,
+ type: 'window'
+ }).then(function(clients) {
+ return clients.find(function(client) {
+ return client.url == backgroundUrl;
+ });
+ });
+ }
+
+ // Returns a Promise wrapper around wakeEventPage, that resolves on success,
+ // or rejects on failure.
+ function makeWakeEventPagePromise() {
+ return new Promise(function(resolve, reject) {
+ wakeEventPage(function(success) {
+ if (success)
+ resolve();
+ else
+ reject('Failed to start background client "' + backgroundUrl + '"');
+ });
+ });
+ }
+
+ // The chrome.runtime.getBackgroundClient function is documented in
+ // runtime.json. It returns a Promise that resolves to the background page's
+ // client, or is rejected if there is no background client or if the
+ // background client failed to wake.
+ self.chrome.runtime.getBackgroundClient = function() {
+ return findBackgroundClient().then(function(client) {
+ if (client) {
+ // Background client is already awake, or it was persistent.
+ return client;
+ }
+
+ // Event page needs to be woken.
+ return makeWakeEventPagePromise().then(function() {
+ return findBackgroundClient();
+ }).then(function(client) {
+ if (!client) {
+ return Promise.reject(
+ 'Background client "' + backgroundUrl + '" not found');
+ }
+ return client;
+ });
+ });
+ };
+});
diff --git a/chromium/extensions/renderer/resources/set_icon.js b/chromium/extensions/renderer/resources/set_icon.js
new file mode 100644
index 00000000000..b0ed2b5b5fa
--- /dev/null
+++ b/chromium/extensions/renderer/resources/set_icon.js
@@ -0,0 +1,112 @@
+// Copyright 2014 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.
+
+var SetIconCommon = requireNative('setIcon').SetIconCommon;
+var sendRequest = require('sendRequest').sendRequest;
+
+function loadImagePath(path, callback) {
+ var img = new Image();
+ img.onerror = function() {
+ console.error('Could not load action icon \'' + path + '\'.');
+ };
+ img.onload = function() {
+ var canvas = document.createElement('canvas');
+ canvas.width = img.width;
+ canvas.height = img.height;
+
+ var canvas_context = canvas.getContext('2d');
+ canvas_context.clearRect(0, 0, canvas.width, canvas.height);
+ canvas_context.drawImage(img, 0, 0, canvas.width, canvas.height);
+ var imageData = canvas_context.getImageData(0, 0, canvas.width,
+ canvas.height);
+ callback(imageData);
+ };
+ img.src = path;
+}
+
+function smellsLikeImageData(imageData) {
+ // See if this object at least looks like an ImageData element.
+ // Unfortunately, we cannot use instanceof because the ImageData
+ // constructor is not public.
+ //
+ // We do this manually instead of using JSONSchema to avoid having these
+ // properties show up in the doc.
+ return (typeof imageData == 'object') && ('width' in imageData) &&
+ ('height' in imageData) && ('data' in imageData);
+}
+
+function verifyImageData(imageData) {
+ if (!smellsLikeImageData(imageData)) {
+ throw new Error(
+ 'The imageData property must contain an ImageData object or' +
+ ' dictionary of ImageData objects.');
+ }
+}
+
+/**
+ * Normalizes |details| to a format suitable for sending to the browser,
+ * for example converting ImageData to a binary representation.
+ *
+ * @param {ImageDetails} details
+ * The ImageDetails passed into an extension action-style API.
+ * @param {Function} callback
+ * The callback function to pass processed imageData back to. Note that this
+ * callback may be called reentrantly.
+ */
+function setIcon(details, callback) {
+ // Note that iconIndex is actually deprecated, and only available to the
+ // pageAction API.
+ // TODO(kalman): Investigate whether this is for the pageActions API, and if
+ // so, delete it.
+ if ('iconIndex' in details) {
+ callback(details);
+ return;
+ }
+
+ if ('imageData' in details) {
+ if (smellsLikeImageData(details.imageData)) {
+ var imageData = details.imageData;
+ details.imageData = {};
+ details.imageData[imageData.width.toString()] = imageData;
+ } else if (typeof details.imageData == 'object' &&
+ Object.getOwnPropertyNames(details.imageData).length !== 0) {
+ for (var sizeKey in details.imageData) {
+ verifyImageData(details.imageData[sizeKey]);
+ }
+ } else {
+ verifyImageData(false);
+ }
+
+ callback(SetIconCommon(details));
+ return;
+ }
+
+ if ('path' in details) {
+ if (typeof details.path == 'object') {
+ details.imageData = {};
+ var detailKeyCount = 0;
+ for (var iconSize in details.path) {
+ ++detailKeyCount;
+ loadImagePath(details.path[iconSize], function(size, imageData) {
+ details.imageData[size] = imageData;
+ if (--detailKeyCount == 0)
+ callback(SetIconCommon(details));
+ }.bind(null, iconSize));
+ }
+ if (detailKeyCount == 0)
+ throw new Error('The path property must not be empty.');
+ } else if (typeof details.path == 'string') {
+ details.imageData = {};
+ loadImagePath(details.path, function(imageData) {
+ details.imageData[imageData.width.toString()] = imageData;
+ delete details.path;
+ callback(SetIconCommon(details));
+ });
+ }
+ return;
+ }
+ throw new Error('Either the path or imageData property must be specified.');
+}
+
+exports.$set('setIcon', setIcon);
diff --git a/chromium/extensions/renderer/resources/stash_client.js b/chromium/extensions/renderer/resources/stash_client.js
new file mode 100644
index 00000000000..240a676463d
--- /dev/null
+++ b/chromium/extensions/renderer/resources/stash_client.js
@@ -0,0 +1,168 @@
+// Copyright 2015 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.
+
+define('stash_client', [
+ 'async_waiter',
+ 'content/public/renderer/frame_service_registry',
+ 'extensions/common/mojo/stash.mojom',
+ 'mojo/public/js/buffer',
+ 'mojo/public/js/codec',
+ 'mojo/public/js/core',
+ 'mojo/public/js/router',
+], function(asyncWaiter, serviceProvider, stashMojom, bufferModule,
+ codec, core, routerModule) {
+ /**
+ * @module stash_client
+ */
+
+ var service = new stashMojom.StashService.proxyClass(new routerModule.Router(
+ serviceProvider.connectToService(stashMojom.StashService.name)));
+
+ /**
+ * A callback invoked to obtain objects to stash from a particular client.
+ * @callback module:stash_client.StashCallback
+ * @return {!Promise<!Array<!Object>>|!Array<!Object>} An array of objects to
+ * stash or a promise that will resolve to an array of objects to stash.
+ * The exact type of each object should match the type passed alongside
+ * this callback.
+ */
+
+ /**
+ * A stash client registration.
+ * @constructor
+ * @private
+ * @alias module:stash_client~Registration
+ */
+ function Registration(id, type, callback) {
+ /**
+ * The client id.
+ * @type {string}
+ * @private
+ */
+ this.id_ = id;
+
+ /**
+ * The type of the objects to be stashed.
+ * @type {!Object}
+ * @private
+ */
+ this.type_ = type;
+
+ /**
+ * The callback to invoke to obtain the objects to stash.
+ * @type {module:stash_client.StashCallback}
+ * @private
+ */
+ this.callback_ = callback;
+ }
+
+ /**
+ * Serializes and returns this client's stashable objects.
+ * @return
+ * {!Promise<!Array<module:extensions/common/stash.mojom.StashedObject>>} The
+ * serialized stashed objects.
+ */
+ Registration.prototype.serialize = function() {
+ return Promise.resolve(this.callback_()).then($Function.bind(
+ function(stashedObjects) {
+ if (!stashedObjects)
+ return [];
+ return $Array.map(stashedObjects, function(stashed) {
+ var builder = new codec.MessageBuilder(
+ 0, codec.align(this.type_.encodedSize));
+ builder.encodeStruct(this.type_, stashed.serialization);
+ var encoded = builder.finish();
+ return new stashMojom.StashedObject({
+ id: this.id_,
+ data: new Uint8Array(encoded.buffer.arrayBuffer),
+ stashed_handles: encoded.handles,
+ monitor_handles: stashed.monitorHandles,
+ });
+ }, this);
+ }, this)).catch(function(e) { return []; });
+ };
+
+ /**
+ * The registered stash clients.
+ * @type {!Array<!Registration>}
+ */
+ var clients = [];
+
+ /**
+ * Registers a client to provide objects to stash during shut-down.
+ *
+ * @param {string} id The id of the client. This can be passed to retrieve to
+ * retrieve the stashed objects.
+ * @param {!Object} type The type of the objects that callback will return.
+ * @param {module:stash_client.StashCallback} callback The callback that
+ * returns objects to stash.
+ * @alias module:stash_client.registerClient
+ */
+ function registerClient(id, type, callback) {
+ clients.push(new Registration(id, type, callback));
+ }
+
+ var retrievedStash = service.retrieveStash().then(function(result) {
+ if (!result || !result.stash)
+ return {};
+ var stashById = {};
+ $Array.forEach(result.stash, function(stashed) {
+ if (!stashById[stashed.id])
+ stashById[stashed.id] = [];
+ stashById[stashed.id].push(stashed);
+ });
+ return stashById;
+ }, function() {
+ // If the stash is not available, act as if the stash was empty.
+ return {};
+ });
+
+ /**
+ * Retrieves the objects that were stashed with the given |id|, deserializing
+ * them into structs with type |type|.
+ *
+ * @param {string} id The id of the client. This should be unique to this
+ * client and should be passed as the id to registerClient().
+ * @param {!Object} type The mojo struct type that was serialized into the
+ * each stashed object.
+ * @return {!Promise<!Array<!Object>>} The stashed objects. The exact type of
+ * each object is that of the |type| parameter.
+ * @alias module:stash_client.retrieve
+ */
+ function retrieve(id, type) {
+ return retrievedStash.then(function(stash) {
+ var stashedObjects = stash[id];
+ if (!stashedObjects)
+ return Promise.resolve([]);
+
+ return Promise.all($Array.map(stashedObjects, function(stashed) {
+ var encodedData = new ArrayBuffer(stashed.data.length);
+ new Uint8Array(encodedData).set(stashed.data);
+ var reader = new codec.MessageReader(new codec.Message(
+ new bufferModule.Buffer(encodedData), stashed.stashed_handles));
+ var decoded = reader.decodeStruct(type);
+ return decoded;
+ }));
+ });
+ }
+
+ function saveStashForTesting() {
+ Promise.all($Array.map(clients, function(client) {
+ return client.serialize();
+ })).then(function(stashedObjects) {
+ var flattenedObjectsToStash = [];
+ $Array.forEach(stashedObjects, function(stashedObjects) {
+ flattenedObjectsToStash =
+ $Array.concat(flattenedObjectsToStash, stashedObjects);
+ });
+ service.addToStash(flattenedObjectsToStash);
+ });
+ }
+
+ return {
+ registerClient: registerClient,
+ retrieve: retrieve,
+ saveStashForTesting: saveStashForTesting,
+ };
+});
diff --git a/chromium/extensions/renderer/resources/storage_area.js b/chromium/extensions/renderer/resources/storage_area.js
new file mode 100644
index 00000000000..4ff6bbdb6d0
--- /dev/null
+++ b/chromium/extensions/renderer/resources/storage_area.js
@@ -0,0 +1,40 @@
+// Copyright 2014 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.
+
+var normalizeArgumentsAndValidate =
+ require('schemaUtils').normalizeArgumentsAndValidate
+var sendRequest = require('sendRequest').sendRequest;
+
+function extendSchema(schema) {
+ var extendedSchema = $Array.slice(schema);
+ $Array.unshift(extendedSchema, {'type': 'string'});
+ return extendedSchema;
+}
+
+function StorageArea(namespace, schema) {
+ // Binds an API function for a namespace to its browser-side call, e.g.
+ // storage.sync.get('foo') -> (binds to) ->
+ // storage.get('sync', 'foo').
+ //
+ // TODO(kalman): Put as a method on CustombindingObject and re-use (or
+ // even generate) for other APIs that need to do this. Same for other
+ // callers of registerCustomType().
+ var self = this;
+ function bindApiFunction(functionName) {
+ self[functionName] = function() {
+ var funSchema = this.functionSchemas[functionName];
+ var args = $Array.slice(arguments);
+ args = normalizeArgumentsAndValidate(args, funSchema);
+ return sendRequest(
+ 'storage.' + functionName,
+ $Array.concat([namespace], args),
+ extendSchema(funSchema.definition.parameters),
+ {preserveNullInObjects: true});
+ };
+ }
+ var apiFunctions = ['get', 'set', 'remove', 'clear', 'getBytesInUse'];
+ $Array.forEach(apiFunctions, bindApiFunction);
+}
+
+exports.$set('StorageArea', StorageArea);
diff --git a/chromium/extensions/renderer/resources/test_custom_bindings.js b/chromium/extensions/renderer/resources/test_custom_bindings.js
new file mode 100644
index 00000000000..6538efac1e3
--- /dev/null
+++ b/chromium/extensions/renderer/resources/test_custom_bindings.js
@@ -0,0 +1,364 @@
+// Copyright 2014 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.
+
+// test_custom_bindings.js
+// mini-framework for ExtensionApiTest browser tests
+
+var binding = require('binding').Binding.create('test');
+
+var environmentSpecificBindings = require('test_environment_specific_bindings');
+var GetExtensionAPIDefinitionsForTest =
+ requireNative('apiDefinitions').GetExtensionAPIDefinitionsForTest;
+var GetAPIFeatures = requireNative('test_features').GetAPIFeatures;
+var natives = requireNative('test_native_handler');
+var uncaughtExceptionHandler = require('uncaught_exception_handler');
+var userGestures = requireNative('user_gestures');
+
+var RunWithNativesEnabled = requireNative('v8_context').RunWithNativesEnabled;
+var GetModuleSystem = requireNative('v8_context').GetModuleSystem;
+
+binding.registerCustomHook(function(api) {
+ var chromeTest = api.compiledApi;
+ var apiFunctions = api.apiFunctions;
+
+ chromeTest.tests = chromeTest.tests || [];
+
+ var currentTest = null;
+ var lastTest = null;
+ var testsFailed = 0;
+ var testCount = 1;
+ var failureException = 'chrome.test.failure';
+
+ // Helper function to get around the fact that function names in javascript
+ // are read-only, and you can't assign one to anonymous functions.
+ function testName(test) {
+ return test ? (test.name || test.generatedName) : "(no test)";
+ }
+
+ function testDone() {
+ environmentSpecificBindings.testDone(chromeTest.runNextTest);
+ }
+
+ function allTestsDone() {
+ if (testsFailed == 0) {
+ chromeTest.notifyPass();
+ } else {
+ chromeTest.notifyFail('Failed ' + testsFailed + ' of ' +
+ testCount + ' tests');
+ }
+ }
+
+ var pendingCallbacks = 0;
+
+ apiFunctions.setHandleRequest('callbackAdded', function() {
+ pendingCallbacks++;
+
+ var called = null;
+ return function() {
+ if (called != null) {
+ var redundantPrefix = 'Error\n';
+ chromeTest.fail(
+ 'Callback has already been run. ' +
+ 'First call:\n' +
+ $String.slice(called, redundantPrefix.length) + '\n' +
+ 'Second call:\n' +
+ $String.slice(new Error().stack, redundantPrefix.length));
+ }
+ called = new Error().stack;
+
+ pendingCallbacks--;
+ if (pendingCallbacks == 0) {
+ chromeTest.succeed();
+ }
+ };
+ });
+
+ apiFunctions.setHandleRequest('runNextTest', function() {
+ // There may have been callbacks which were interrupted by failure
+ // exceptions.
+ pendingCallbacks = 0;
+
+ lastTest = currentTest;
+ currentTest = chromeTest.tests.shift();
+
+ if (!currentTest) {
+ allTestsDone();
+ return;
+ }
+
+ try {
+ chromeTest.log("( RUN ) " + testName(currentTest));
+ uncaughtExceptionHandler.setHandler(function(message, e) {
+ if (e !== failureException)
+ chromeTest.fail('uncaught exception: ' + message);
+ });
+ currentTest.call();
+ } catch (e) {
+ uncaughtExceptionHandler.handle(e.message, e);
+ }
+ });
+
+ apiFunctions.setHandleRequest('fail', function(message) {
+ chromeTest.log("( FAILED ) " + testName(currentTest));
+
+ var stack = {};
+ Error.captureStackTrace(stack, chromeTest.fail);
+
+ if (!message)
+ message = "FAIL (no message)";
+
+ message += "\n" + stack.stack;
+ console.log("[FAIL] " + testName(currentTest) + ": " + message);
+ testsFailed++;
+ testDone();
+
+ // Interrupt the rest of the test.
+ throw failureException;
+ });
+
+ apiFunctions.setHandleRequest('succeed', function() {
+ console.log("[SUCCESS] " + testName(currentTest));
+ chromeTest.log("( SUCCESS )");
+ testDone();
+ });
+
+ apiFunctions.setHandleRequest('runWithNativesEnabled', function(callback) {
+ RunWithNativesEnabled(callback);
+ });
+
+ apiFunctions.setHandleRequest('getModuleSystem', function(context) {
+ return GetModuleSystem(context);
+ });
+
+ apiFunctions.setHandleRequest('assertTrue', function(test, message) {
+ chromeTest.assertBool(test, true, message);
+ });
+
+ apiFunctions.setHandleRequest('assertFalse', function(test, message) {
+ chromeTest.assertBool(test, false, message);
+ });
+
+ apiFunctions.setHandleRequest('assertBool',
+ function(test, expected, message) {
+ if (test !== expected) {
+ if (typeof(test) == "string") {
+ if (message)
+ message = test + "\n" + message;
+ else
+ message = test;
+ }
+ chromeTest.fail(message);
+ }
+ });
+
+ apiFunctions.setHandleRequest('checkDeepEq', function(expected, actual) {
+ if ((expected === null) != (actual === null))
+ return false;
+
+ if (expected === actual)
+ return true;
+
+ if (typeof(expected) !== typeof(actual))
+ return false;
+
+ for (var p in actual) {
+ if ($Object.hasOwnProperty(actual, p) &&
+ !$Object.hasOwnProperty(expected, p)) {
+ return false;
+ }
+ }
+ for (var p in expected) {
+ if ($Object.hasOwnProperty(expected, p) &&
+ !$Object.hasOwnProperty(actual, p)) {
+ return false;
+ }
+ }
+
+ for (var p in expected) {
+ var eq = true;
+ switch (typeof(expected[p])) {
+ case 'object':
+ eq = chromeTest.checkDeepEq(expected[p], actual[p]);
+ break;
+ case 'function':
+ eq = (typeof(actual[p]) != 'undefined' &&
+ expected[p].toString() == actual[p].toString());
+ break;
+ default:
+ eq = (expected[p] == actual[p] &&
+ typeof(expected[p]) == typeof(actual[p]));
+ break;
+ }
+ if (!eq)
+ return false;
+ }
+ return true;
+ });
+
+ apiFunctions.setHandleRequest('assertEq',
+ function(expected, actual, message) {
+ var error_msg = "API Test Error in " + testName(currentTest);
+ if (message)
+ error_msg += ": " + message;
+ if (typeof(expected) == 'object') {
+ if (!chromeTest.checkDeepEq(expected, actual)) {
+ error_msg += "\nActual: " + $JSON.stringify(actual) +
+ "\nExpected: " + $JSON.stringify(expected);
+ chromeTest.fail(error_msg);
+ }
+ return;
+ }
+ if (expected != actual) {
+ chromeTest.fail(error_msg +
+ "\nActual: " + actual + "\nExpected: " + expected);
+ }
+ if (typeof(expected) != typeof(actual)) {
+ chromeTest.fail(error_msg +
+ " (type mismatch)\nActual Type: " + typeof(actual) +
+ "\nExpected Type:" + typeof(expected));
+ }
+ });
+
+ apiFunctions.setHandleRequest('assertNoLastError', function() {
+ if (chrome.runtime.lastError != undefined) {
+ chromeTest.fail("lastError.message == " +
+ chrome.runtime.lastError.message);
+ }
+ });
+
+ apiFunctions.setHandleRequest('assertLastError', function(expectedError) {
+ chromeTest.assertEq(typeof(expectedError), 'string');
+ chromeTest.assertTrue(chrome.runtime.lastError != undefined,
+ "No lastError, but expected " + expectedError);
+ chromeTest.assertEq(expectedError, chrome.runtime.lastError.message);
+ });
+
+ apiFunctions.setHandleRequest('assertThrows',
+ function(fn, self, args, message) {
+ chromeTest.assertTrue(typeof fn == 'function');
+ try {
+ fn.apply(self, args);
+ chromeTest.fail('Did not throw error: ' + fn);
+ } catch (e) {
+ if (e != failureException && message !== undefined) {
+ if (message instanceof RegExp) {
+ chromeTest.assertTrue(message.test(e.message),
+ e.message + ' should match ' + message)
+ } else {
+ chromeTest.assertEq(message, e.message);
+ }
+ }
+ }
+ });
+
+ function safeFunctionApply(func, args) {
+ try {
+ if (func)
+ return $Function.apply(func, undefined, args);
+ } catch (e) {
+ var msg = "uncaught exception " + e;
+ chromeTest.fail(msg);
+ }
+ };
+
+ // Wrapper for generating test functions, that takes care of calling
+ // assertNoLastError() and (optionally) succeed() for you.
+ apiFunctions.setHandleRequest('callback', function(func, expectedError) {
+ if (func) {
+ chromeTest.assertEq(typeof(func), 'function');
+ }
+ var callbackCompleted = chromeTest.callbackAdded();
+
+ return function() {
+ if (expectedError == null) {
+ chromeTest.assertNoLastError();
+ } else {
+ chromeTest.assertLastError(expectedError);
+ }
+
+ var result;
+ if (func) {
+ result = safeFunctionApply(func, arguments);
+ }
+
+ callbackCompleted();
+ return result;
+ };
+ });
+
+ apiFunctions.setHandleRequest('listenOnce', function(event, func) {
+ var callbackCompleted = chromeTest.callbackAdded();
+ var listener = function() {
+ event.removeListener(listener);
+ safeFunctionApply(func, arguments);
+ callbackCompleted();
+ };
+ event.addListener(listener);
+ });
+
+ apiFunctions.setHandleRequest('listenForever', function(event, func) {
+ var callbackCompleted = chromeTest.callbackAdded();
+
+ var listener = function() {
+ safeFunctionApply(func, arguments);
+ };
+
+ var done = function() {
+ event.removeListener(listener);
+ callbackCompleted();
+ };
+
+ event.addListener(listener);
+ return done;
+ });
+
+ apiFunctions.setHandleRequest('callbackPass', function(func) {
+ return chromeTest.callback(func);
+ });
+
+ apiFunctions.setHandleRequest('callbackFail', function(expectedError, func) {
+ return chromeTest.callback(func, expectedError);
+ });
+
+ apiFunctions.setHandleRequest('runTests', function(tests) {
+ chromeTest.tests = tests;
+ testCount = chromeTest.tests.length;
+ chromeTest.runNextTest();
+ });
+
+ apiFunctions.setHandleRequest('getApiDefinitions', function() {
+ return GetExtensionAPIDefinitionsForTest();
+ });
+
+ apiFunctions.setHandleRequest('getApiFeatures', function() {
+ return GetAPIFeatures();
+ });
+
+ apiFunctions.setHandleRequest('isProcessingUserGesture', function() {
+ return userGestures.IsProcessingUserGesture();
+ });
+
+ apiFunctions.setHandleRequest('runWithUserGesture', function(callback) {
+ chromeTest.assertEq(typeof(callback), 'function');
+ return userGestures.RunWithUserGesture(callback);
+ });
+
+ apiFunctions.setHandleRequest('runWithoutUserGesture', function(callback) {
+ chromeTest.assertEq(typeof(callback), 'function');
+ return userGestures.RunWithoutUserGesture(callback);
+ });
+
+ apiFunctions.setHandleRequest('setExceptionHandler', function(callback) {
+ chromeTest.assertEq(typeof(callback), 'function');
+ uncaughtExceptionHandler.setHandler(callback);
+ });
+
+ apiFunctions.setHandleRequest('getWakeEventPage', function() {
+ return natives.GetWakeEventPage();
+ });
+
+ environmentSpecificBindings.registerHooks(api);
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/uncaught_exception_handler.js b/chromium/extensions/renderer/resources/uncaught_exception_handler.js
new file mode 100644
index 00000000000..de4d71e71dc
--- /dev/null
+++ b/chromium/extensions/renderer/resources/uncaught_exception_handler.js
@@ -0,0 +1,110 @@
+// Copyright 2014 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.
+
+// Handles uncaught exceptions thrown by extensions. By default this is to
+// log an error message, but tests may override this behaviour.
+var handler = function(message, e) {
+ console.error(message);
+};
+
+/**
+ * Append the error description and stack trace to |message|.
+ *
+ * @param {string} message - The prefix of the error message.
+ * @param {Error|*} e - The thrown error object. This object is potentially
+ * unsafe, because it could be generated by an extension.
+ * @param {string=} priorStackTrace - The stack trace to be appended to the
+ * error message. This stack trace must not include stack frames of |e.stack|,
+ * because both stack traces are concatenated. Overlapping stack traces will
+ * confuse extension developers.
+ * @return {string} The formatted error message.
+ */
+function formatErrorMessage(message, e, priorStackTrace) {
+ if (e)
+ message += ': ' + safeErrorToString(e, false);
+
+ var stack;
+ try {
+ // If the stack was set, use it.
+ // |e.stack| could be void in the following common example:
+ // throw "Error message";
+ stack = $String.self(e && e.stack);
+ } catch (e) {}
+
+ // If a stack is not provided, capture a stack trace.
+ if (!priorStackTrace && !stack)
+ stack = getStackTrace();
+
+ stack = filterExtensionStackTrace(stack);
+ if (stack)
+ message += '\n' + stack;
+
+ // If an asynchronouse stack trace was set, append it.
+ if (priorStackTrace)
+ message += '\n' + priorStackTrace;
+
+ return message;
+}
+
+function filterExtensionStackTrace(stack) {
+ if (!stack)
+ return '';
+ // Remove stack frames in the stack trace that weren't associated with the
+ // extension, to not confuse extension developers with internal details.
+ stack = $String.split(stack, '\n');
+ stack = $Array.filter(stack, function(line) {
+ return $String.indexOf(line, 'chrome-extension://') >= 0;
+ });
+ return $Array.join(stack, '\n');
+}
+
+function getStackTrace() {
+ var e = {};
+ $Error.captureStackTrace(e, getStackTrace);
+ return e.stack;
+}
+
+function getExtensionStackTrace() {
+ return filterExtensionStackTrace(getStackTrace());
+}
+
+/**
+ * Convert an object to a string.
+ *
+ * @param {Error|*} e - A thrown object (possibly user-supplied).
+ * @param {boolean=} omitType - Whether to try to serialize |e.message| instead
+ * of |e.toString()|.
+ * @return {string} The error message.
+ */
+function safeErrorToString(e, omitType) {
+ try {
+ return $String.self(omitType && e.message || e);
+ } catch (e) {
+ // This error is exceptional and could be triggered by
+ // throw {toString: function() { throw 'Haha' } };
+ return '(cannot get error message)';
+ }
+}
+
+/**
+ * Formats the error message and invokes the error handler.
+ *
+ * @param {string} message - Error message prefix.
+ * @param {Error|*} e - Thrown object.
+ * @param {string=} priorStackTrace - Error message suffix.
+ * @see formatErrorMessage
+ */
+exports.$set('handle', function(message, e, priorStackTrace) {
+ message = formatErrorMessage(message, e, priorStackTrace);
+ handler(message, e);
+});
+
+// |newHandler| A function which matches |handler|.
+exports.$set('setHandler', function(newHandler) {
+ handler = newHandler;
+});
+
+exports.$set('getStackTrace', getStackTrace);
+exports.$set('getExtensionStackTrace', getExtensionStackTrace);
+exports.$set('safeErrorToString', safeErrorToString);
diff --git a/chromium/extensions/renderer/resources/utils.js b/chromium/extensions/renderer/resources/utils.js
new file mode 100644
index 00000000000..26aa5e8ed3e
--- /dev/null
+++ b/chromium/extensions/renderer/resources/utils.js
@@ -0,0 +1,240 @@
+// Copyright 2014 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.
+
+var nativeDeepCopy = requireNative('utils').deepCopy;
+var schemaRegistry = requireNative('schema_registry');
+var CHECK = requireNative('logging').CHECK;
+var DCHECK = requireNative('logging').DCHECK;
+var WARNING = requireNative('logging').WARNING;
+
+/**
+ * An object forEach. Calls |f| with each (key, value) pair of |obj|, using
+ * |self| as the target.
+ * @param {Object} obj The object to iterate over.
+ * @param {function} f The function to call in each iteration.
+ * @param {Object} self The object to use as |this| in each function call.
+ */
+function forEach(obj, f, self) {
+ for (var key in obj) {
+ if ($Object.hasOwnProperty(obj, key))
+ $Function.call(f, self, key, obj[key]);
+ }
+}
+
+/**
+ * Assuming |array_of_dictionaries| is structured like this:
+ * [{id: 1, ... }, {id: 2, ...}, ...], you can use
+ * lookup(array_of_dictionaries, 'id', 2) to get the dictionary with id == 2.
+ * @param {Array<Object<?>>} array_of_dictionaries
+ * @param {string} field
+ * @param {?} value
+ */
+function lookup(array_of_dictionaries, field, value) {
+ var filter = function (dict) {return dict[field] == value;};
+ var matches = $Array.filter(array_of_dictionaries, filter);
+ if (matches.length == 0) {
+ return undefined;
+ } else if (matches.length == 1) {
+ return matches[0]
+ } else {
+ throw new Error("Failed lookup of field '" + field + "' with value '" +
+ value + "'");
+ }
+}
+
+function loadTypeSchema(typeName, defaultSchema) {
+ var parts = $String.split(typeName, '.');
+ if (parts.length == 1) {
+ if (defaultSchema == null) {
+ WARNING('Trying to reference "' + typeName + '" ' +
+ 'with neither namespace nor default schema.');
+ return null;
+ }
+ var types = defaultSchema.types;
+ } else {
+ var schemaName = $Array.join($Array.slice(parts, 0, parts.length - 1), '.');
+ var types = schemaRegistry.GetSchema(schemaName).types;
+ }
+ for (var i = 0; i < types.length; ++i) {
+ if (types[i].id == typeName)
+ return types[i];
+ }
+ return null;
+}
+
+/**
+ * Sets a property |value| on |obj| with property name |key|. Like
+ *
+ * obj[key] = value;
+ *
+ * but without triggering setters.
+ */
+function defineProperty(obj, key, value) {
+ $Object.defineProperty(obj, key, {
+ __proto__: null,
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: value,
+ });
+}
+
+/**
+ * Takes a private class implementation |privateClass| and exposes a subset of
+ * its methods |functions| and properties |properties| and |readonly| to a
+ * public wrapper class that should be passed in. Within bindings code, you can
+ * access the implementation from an instance of the wrapper class using
+ * privates(instance).impl, and from the implementation class you can access
+ * the wrapper using this.wrapper (or implInstance.wrapper if you have another
+ * instance of the implementation class).
+ *
+ * |publicClass| should be a constructor that calls constructPrivate() like so:
+ *
+ * privates(publicClass).constructPrivate(this, arguments);
+ *
+ * @param {function} publicClass The publicly exposed wrapper class. This must
+ * be a named function, and the name appears in stack traces.
+ * @param {Object} privateClass The class implementation.
+ * @param {{superclass: ?Function,
+ * functions: ?Array<string>,
+ * properties: ?Array<string>,
+ * readonly: ?Array<string>}} exposed The names of properties on the
+ * implementation class to be exposed. |superclass| represents the
+ * constructor of the class to be used as the superclass of the exposed
+ * class; |functions| represents the names of functions which should be
+ * delegated to the implementation; |properties| are gettable/settable
+ * properties and |readonly| are read-only properties.
+ */
+function expose(publicClass, privateClass, exposed) {
+ DCHECK(!(privateClass.prototype instanceof $Object.self));
+
+ $Object.setPrototypeOf(exposed, null);
+
+ // This should be called by publicClass.
+ privates(publicClass).constructPrivate = function(self, args) {
+ if (!(self instanceof publicClass)) {
+ throw new Error('Please use "new ' + publicClass.name + '"');
+ }
+ // The "instanceof publicClass" check can easily be spoofed, so we check
+ // whether the private impl is already set before continuing.
+ var privateSelf = privates(self);
+ if ('impl' in privateSelf) {
+ throw new Error('Object ' + publicClass.name + ' is already constructed');
+ }
+ var privateObj = $Object.create(privateClass.prototype);
+ $Function.apply(privateClass, privateObj, args);
+ privateObj.wrapper = self;
+ privateSelf.impl = privateObj;
+ };
+
+ function getPrivateImpl(self) {
+ var impl = privates(self).impl;
+ if (!(impl instanceof privateClass)) {
+ // Either the object is not constructed, or the property descriptor is
+ // used on a target that is not an instance of publicClass.
+ throw new Error('impl is not an instance of ' + privateClass.name);
+ }
+ return impl;
+ }
+
+ var publicClassPrototype = {
+ // The final prototype will be assigned at the end of this method.
+ __proto__: null,
+ constructor: publicClass,
+ };
+
+ if ('functions' in exposed) {
+ $Array.forEach(exposed.functions, function(func) {
+ publicClassPrototype[func] = function() {
+ var impl = getPrivateImpl(this);
+ return $Function.apply(impl[func], impl, arguments);
+ };
+ });
+ }
+
+ if ('properties' in exposed) {
+ $Array.forEach(exposed.properties, function(prop) {
+ $Object.defineProperty(publicClassPrototype, prop, {
+ __proto__: null,
+ enumerable: true,
+ get: function() {
+ return getPrivateImpl(this)[prop];
+ },
+ set: function(value) {
+ var impl = getPrivateImpl(this);
+ delete impl[prop];
+ impl[prop] = value;
+ }
+ });
+ });
+ }
+
+ if ('readonly' in exposed) {
+ $Array.forEach(exposed.readonly, function(readonly) {
+ $Object.defineProperty(publicClassPrototype, readonly, {
+ __proto__: null,
+ enumerable: true,
+ get: function() {
+ return getPrivateImpl(this)[readonly];
+ },
+ });
+ });
+ }
+
+ // The prototype properties have been installed. Now we can safely assign an
+ // unsafe prototype and export the class to the public.
+ var superclass = exposed.superclass || $Object.self;
+ $Object.setPrototypeOf(publicClassPrototype, superclass.prototype);
+ publicClass.prototype = publicClassPrototype;
+
+ return publicClass;
+}
+
+/**
+ * Returns a deep copy of |value|. The copy will have no references to nested
+ * values of |value|.
+ */
+function deepCopy(value) {
+ return nativeDeepCopy(value);
+}
+
+/**
+ * Wrap an asynchronous API call to a function |func| in a promise. The
+ * remaining arguments will be passed to |func|. Returns a promise that will be
+ * resolved to the result passed to the callback or rejected if an error occurs
+ * (if chrome.runtime.lastError is set). If there are multiple results, the
+ * promise will be resolved with an array containing those results.
+ *
+ * For example,
+ * promise(chrome.storage.get, 'a').then(function(result) {
+ * // Use result.
+ * }).catch(function(error) {
+ * // Report error.message.
+ * });
+ */
+function promise(func) {
+ var args = $Array.slice(arguments, 1);
+ DCHECK(typeof func == 'function');
+ return new Promise(function(resolve, reject) {
+ args.push(function() {
+ if (chrome.runtime.lastError) {
+ reject(new Error(chrome.runtime.lastError));
+ return;
+ }
+ if (arguments.length <= 1)
+ resolve(arguments[0]);
+ else
+ resolve($Array.slice(arguments));
+ });
+ $Function.apply(func, null, args);
+ });
+}
+
+exports.$set('forEach', forEach);
+exports.$set('loadTypeSchema', loadTypeSchema);
+exports.$set('lookup', lookup);
+exports.$set('defineProperty', defineProperty);
+exports.$set('expose', expose);
+exports.$set('deepCopy', deepCopy);
+exports.$set('promise', promise);
diff --git a/chromium/extensions/renderer/resources/web_request_custom_bindings.js b/chromium/extensions/renderer/resources/web_request_custom_bindings.js
new file mode 100644
index 00000000000..2dd7ce4f048
--- /dev/null
+++ b/chromium/extensions/renderer/resources/web_request_custom_bindings.js
@@ -0,0 +1,23 @@
+// 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.
+
+// Custom binding for the webRequest API.
+
+var binding = require('binding').Binding.create('webRequest');
+var sendRequest = require('sendRequest').sendRequest;
+var WebRequestEvent = require('webRequestInternal').WebRequestEvent;
+
+binding.registerCustomHook(function(api) {
+ var apiFunctions = api.apiFunctions;
+
+ apiFunctions.setHandleRequest('handlerBehaviorChanged', function() {
+ var args = $Array.slice(arguments);
+ sendRequest(this.name, args, this.definition.parameters,
+ {forIOThread: true});
+ });
+});
+
+binding.registerCustomEvent(WebRequestEvent);
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/web_request_internal_custom_bindings.js b/chromium/extensions/renderer/resources/web_request_internal_custom_bindings.js
new file mode 100644
index 00000000000..32ad33b9b47
--- /dev/null
+++ b/chromium/extensions/renderer/resources/web_request_internal_custom_bindings.js
@@ -0,0 +1,196 @@
+// 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.
+
+// Custom binding for the webRequestInternal API.
+
+var binding = require('binding').Binding.create('webRequestInternal');
+var eventBindings = require('event_bindings');
+var sendRequest = require('sendRequest').sendRequest;
+var validate = require('schemaUtils').validate;
+var utils = require('utils');
+var idGeneratorNatives = requireNative('id_generator');
+
+var webRequestInternal;
+
+function GetUniqueSubEventName(eventName) {
+ return eventName + '/' + idGeneratorNatives.GetNextId();
+}
+
+// WebRequestEventImpl object. This is used for special webRequest events
+// with extra parameters. Each invocation of addListener creates a new named
+// sub-event. That sub-event is associated with the extra parameters in the
+// browser process, so that only it is dispatched when the main event occurs
+// matching the extra parameters.
+//
+// Example:
+// chrome.webRequest.onBeforeRequest.addListener(
+// callback, {urls: 'http://*.google.com/*'});
+// ^ callback will only be called for onBeforeRequests matching the filter.
+function WebRequestEventImpl(eventName, opt_argSchemas, opt_extraArgSchemas,
+ opt_eventOptions, opt_webViewInstanceId) {
+ if (typeof eventName != 'string')
+ throw new Error('chrome.WebRequestEvent requires an event name.');
+
+ this.eventName = eventName;
+ this.argSchemas = opt_argSchemas;
+ this.extraArgSchemas = opt_extraArgSchemas;
+ this.webViewInstanceId = opt_webViewInstanceId || 0;
+ this.subEvents = [];
+ this.eventOptions = eventBindings.parseEventOptions(opt_eventOptions);
+ if (this.eventOptions.supportsRules) {
+ this.eventForRules =
+ new eventBindings.Event(eventName, opt_argSchemas, opt_eventOptions,
+ opt_webViewInstanceId);
+ }
+}
+$Object.setPrototypeOf(WebRequestEventImpl.prototype, null);
+
+// Test if the given callback is registered for this event.
+WebRequestEventImpl.prototype.hasListener = function(cb) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error('This event does not support listeners.');
+ return this.findListener_(cb) > -1;
+};
+
+// Test if any callbacks are registered fur thus event.
+WebRequestEventImpl.prototype.hasListeners = function() {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error('This event does not support listeners.');
+ return this.subEvents.length > 0;
+};
+
+// Registers a callback to be called when this event is dispatched. If
+// opt_filter is specified, then the callback is only called for events that
+// match the given filters. If opt_extraInfo is specified, the given optional
+// info is sent to the callback.
+WebRequestEventImpl.prototype.addListener =
+ function(cb, opt_filter, opt_extraInfo) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error('This event does not support listeners.');
+ // NOTE(benjhayden) New APIs should not use this subEventName trick! It does
+ // not play well with event pages. See downloads.onDeterminingFilename and
+ // ExtensionDownloadsEventRouter for an alternative approach.
+ var subEventName = GetUniqueSubEventName(this.eventName);
+ // Note: this could fail to validate, in which case we would not add the
+ // subEvent listener.
+ validate($Array.slice(arguments, 1), this.extraArgSchemas);
+ webRequestInternal.addEventListener(
+ cb, opt_filter, opt_extraInfo, this.eventName, subEventName,
+ this.webViewInstanceId);
+
+ var subEvent = new eventBindings.Event(subEventName, this.argSchemas);
+ var subEventCallback = cb;
+ if (opt_extraInfo && opt_extraInfo.indexOf('blocking') >= 0) {
+ var eventName = this.eventName;
+ subEventCallback = function() {
+ var requestId = arguments[0].requestId;
+ try {
+ var result = $Function.apply(cb, null, arguments);
+ webRequestInternal.eventHandled(
+ eventName, subEventName, requestId, result);
+ } catch (e) {
+ webRequestInternal.eventHandled(
+ eventName, subEventName, requestId);
+ throw e;
+ }
+ };
+ } else if (opt_extraInfo && opt_extraInfo.indexOf('asyncBlocking') >= 0) {
+ var eventName = this.eventName;
+ subEventCallback = function() {
+ var details = arguments[0];
+ var requestId = details.requestId;
+ var handledCallback = function(response) {
+ webRequestInternal.eventHandled(
+ eventName, subEventName, requestId, response);
+ };
+ $Function.apply(cb, null, [details, handledCallback]);
+ };
+ }
+ $Array.push(this.subEvents,
+ {subEvent: subEvent, callback: cb, subEventCallback: subEventCallback});
+ subEvent.addListener(subEventCallback);
+};
+
+// Unregisters a callback.
+WebRequestEventImpl.prototype.removeListener = function(cb) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error('This event does not support listeners.');
+ var idx;
+ while ((idx = this.findListener_(cb)) >= 0) {
+ var e = this.subEvents[idx];
+ e.subEvent.removeListener(e.subEventCallback);
+ if (e.subEvent.hasListeners()) {
+ console.error(
+ 'Internal error: webRequest subEvent has orphaned listeners.');
+ }
+ $Array.splice(this.subEvents, idx, 1);
+ }
+};
+
+WebRequestEventImpl.prototype.findListener_ = function(cb) {
+ for (var i in this.subEvents) {
+ var e = this.subEvents[i];
+ if (e.callback === cb) {
+ if (e.subEvent.hasListener(e.subEventCallback))
+ return i;
+ console.error('Internal error: webRequest subEvent has no callback.');
+ }
+ }
+
+ return -1;
+};
+
+WebRequestEventImpl.prototype.addRules = function(rules, opt_cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error('This event does not support rules.');
+ this.eventForRules.addRules(rules, opt_cb);
+};
+
+WebRequestEventImpl.prototype.removeRules =
+ function(ruleIdentifiers, opt_cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error('This event does not support rules.');
+ this.eventForRules.removeRules(ruleIdentifiers, opt_cb);
+};
+
+WebRequestEventImpl.prototype.getRules = function(ruleIdentifiers, cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error('This event does not support rules.');
+ this.eventForRules.getRules(ruleIdentifiers, cb);
+};
+
+binding.registerCustomHook(function(api) {
+ var apiFunctions = api.apiFunctions;
+
+ apiFunctions.setHandleRequest('addEventListener', function() {
+ var args = $Array.slice(arguments);
+ sendRequest(this.name, args, this.definition.parameters,
+ {forIOThread: true});
+ });
+
+ apiFunctions.setHandleRequest('eventHandled', function() {
+ var args = $Array.slice(arguments);
+ sendRequest(this.name, args, this.definition.parameters,
+ {forIOThread: true});
+ });
+});
+
+function WebRequestEvent() {
+ privates(WebRequestEvent).constructPrivate(this, arguments);
+}
+utils.expose(WebRequestEvent, WebRequestEventImpl, {
+ functions: [
+ 'hasListener',
+ 'hasListeners',
+ 'addListener',
+ 'removeListener',
+ 'addRules',
+ 'removeRules',
+ 'getRules',
+ ],
+});
+
+webRequestInternal = binding.generate();
+exports.$set('binding', webRequestInternal);
+exports.$set('WebRequestEvent', WebRequestEvent);
diff --git a/chromium/extensions/renderer/resources/window_controls.js b/chromium/extensions/renderer/resources/window_controls.js
new file mode 100644
index 00000000000..75c88e63927
--- /dev/null
+++ b/chromium/extensions/renderer/resources/window_controls.js
@@ -0,0 +1,78 @@
+// Copyright 2013 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.
+
+//
+// <window-controls> shadow element implementation.
+//
+
+var chrome = requireNative('chrome').GetChrome();
+var forEach = require('utils').forEach;
+var addTagWatcher = require('tagWatcher').addTagWatcher;
+var appWindow = require('app.window');
+var getHtmlTemplate =
+ requireNative('app_window_natives').GetWindowControlsHtmlTemplate;
+
+/**
+ * @constructor
+ */
+function WindowControls(node) {
+ this.node_ = node;
+ this.shadowRoot_ = this.createShadowRoot_(node);
+ this.setupWindowControls_();
+}
+
+/**
+ * @private
+ */
+WindowControls.prototype.template_element = null;
+
+/**
+ * @private
+ */
+WindowControls.prototype.createShadowRoot_ = function(node) {
+ // Initialize |template| from HTML template resource and cache result.
+ var template = WindowControls.prototype.template_element;
+ if (!template) {
+ var element = document.createElement('div');
+ element.innerHTML = getHtmlTemplate();
+ WindowControls.prototype.template_element = element.firstChild;
+ template = WindowControls.prototype.template_element;
+ }
+ // Create shadow root element with template clone as first child.
+ var shadowRoot = node.createShadowRoot();
+ shadowRoot.appendChild(template.content.cloneNode(true));
+ return shadowRoot;
+}
+
+/**
+ * @private
+ */
+WindowControls.prototype.setupWindowControls_ = function() {
+ var self = this;
+ this.shadowRoot_.querySelector("#close-control").addEventListener('click',
+ function(e) {
+ chrome.app.window.current().close();
+ });
+
+ this.shadowRoot_.querySelector("#maximize-control").addEventListener('click',
+ function(e) {
+ self.maxRestore_();
+ });
+}
+
+/**
+ * @private
+ * Restore or maximize depending on current state
+ */
+WindowControls.prototype.maxRestore_ = function() {
+ if (chrome.app.window.current().isMaximized()) {
+ chrome.app.window.current().restore();
+ } else {
+ chrome.app.window.current().maximize();
+ }
+}
+
+addTagWatcher('WINDOW-CONTROLS', function(addedNode) {
+ new WindowControls(addedNode);
+});
diff --git a/chromium/extensions/renderer/resources/window_controls_template.html b/chromium/extensions/renderer/resources/window_controls_template.html
new file mode 100644
index 00000000000..6f468bc4b3a
--- /dev/null
+++ b/chromium/extensions/renderer/resources/window_controls_template.html
@@ -0,0 +1,52 @@
+<template id="window-controls-template">
+ <style>
+ .controls {
+ width:32px;
+ height:32px;
+ position:absolute;
+ z-index:200;
+ }
+ #close-control {
+ top:8px;
+ right:10px;
+ }
+ #maximize-control {
+ top:8px;
+ right:52px;
+ }
+ #close {
+ top:0;
+ right:0;
+ -webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAALNJREFUeNrsllEOwyAIhmVXwIu487dXKEdy2PBgWuxwm/VhkPwvSuALAhFyzmGmPcJkcwAHcAAHMAMAQGItLGSFhlB8kpmgrGKL2NbiziIWKqHK2SY+qzluBwBKcg2iTr7fjQBoQZySd1W2E0CD2LSqjAQ4Qqh9YY37zRj+5iPx4RPUZac7n6DVhHRHE74bQxo9hvUiio1FRCMXURKIeNFSKD5Pa1zwX7EDOIAD/D3AS4ABAKWdkCCeGGsrAAAAAElFTkSuQmCC');
+ }
+ .windowbutton {
+ width:32px;
+ height:32px;
+ position:absolute;
+ background-color:rgba(0, 0, 0, 0.49);
+ }
+ .windowbuttonbackground {
+ width:32px;
+ height:32px;
+ position:absolute;
+ }
+ .windowbuttonbackground:hover {
+ background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEBJREFUeNrszrENAEAIw8A8EvU32X9RGsQUaewFfM/2l9TKNBWcX10KBwAAAAAAAAAAAAAAAAAAABxggv9ZAQYAhakDi3I15kgAAAAASUVORK5CYII=');
+
+ }
+ .windowbuttonbackground:active {
+ background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFtJREFUeNrs1zESgCAMRNEAkSZD5VW8/1k8hFaCCqfY5u9M6v/apIh2mH1hkqXba92uUvxU5Mfoe57xx0Rb7WziAQAAAAAAAAAAAAAAAAAAOcDXkzqvifrvL8AAWBcLpapo5CcAAAAASUVORK5CYII=');
+ }
+ #maximize {
+ -webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFFJREFUeNrs1jsOACAIREHWeP8royWdnwaDj9pi2OhGubtlTrPkAQAAAIC+e1DSUWXOhlWtBGIYq+W5hAAA1GzC23f+fALiVwwAAIDvAUOAAQAv/Aw+jTHzugAAAABJRU5ErkJggg==');
+ }
+ </style>
+ <div id="close-control" class="controls">
+ <div id="close" class="windowbutton"> </div>
+ <div id="close-background" class="windowbuttonbackground"> </div>
+ </div>
+ <div id="maximize-control" class="controls">
+ <div id="maximize" class="windowbutton"></div>
+ <div id="maximize-background" class="windowbuttonbackground"></div>
+ </div>
+</template>
diff --git a/chromium/extensions/renderer/runtime_custom_bindings.cc b/chromium/extensions/renderer/runtime_custom_bindings.cc
new file mode 100644
index 00000000000..8c6f2705e7e
--- /dev/null
+++ b/chromium/extensions/renderer/runtime_custom_bindings.cc
@@ -0,0 +1,184 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/runtime_custom_bindings.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/manifest.h"
+#include "extensions/renderer/api_activity_logger.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+RuntimeCustomBindings::RuntimeCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetManifest",
+ base::Bind(&RuntimeCustomBindings::GetManifest, base::Unretained(this)));
+ RouteFunction("OpenChannelToExtension",
+ base::Bind(&RuntimeCustomBindings::OpenChannelToExtension,
+ base::Unretained(this)));
+ RouteFunction("OpenChannelToNativeApp",
+ base::Bind(&RuntimeCustomBindings::OpenChannelToNativeApp,
+ base::Unretained(this)));
+ RouteFunction("GetExtensionViews",
+ base::Bind(&RuntimeCustomBindings::GetExtensionViews,
+ base::Unretained(this)));
+}
+
+RuntimeCustomBindings::~RuntimeCustomBindings() {
+}
+
+void RuntimeCustomBindings::OpenChannelToExtension(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Get the current RenderFrame so that we can send a routed IPC message from
+ // the correct source.
+ content::RenderFrame* renderframe = context()->GetRenderFrame();
+ if (!renderframe)
+ return;
+
+ // The Javascript code should validate/fill the arguments.
+ CHECK_EQ(args.Length(), 3);
+ CHECK(args[0]->IsString() && args[1]->IsString() && args[2]->IsBoolean());
+
+ ExtensionMsg_ExternalConnectionInfo info;
+
+ // For messaging APIs, hosted apps should be considered a web page so hide
+ // its extension ID.
+ const Extension* extension = context()->extension();
+ if (extension && !extension->is_hosted_app())
+ info.source_id = extension->id();
+
+ info.target_id = *v8::String::Utf8Value(args[0]);
+ info.source_url = context()->url();
+ std::string channel_name = *v8::String::Utf8Value(args[1]);
+ bool include_tls_channel_id =
+ args.Length() > 2 ? args[2]->BooleanValue() : false;
+ int port_id = -1;
+ renderframe->Send(new ExtensionHostMsg_OpenChannelToExtension(
+ renderframe->GetRoutingID(), info, channel_name, include_tls_channel_id,
+ &port_id));
+ args.GetReturnValue().Set(static_cast<int32_t>(port_id));
+}
+
+void RuntimeCustomBindings::OpenChannelToNativeApp(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Verify that the extension has permission to use native messaging.
+ Feature::Availability availability =
+ FeatureProvider::GetPermissionFeatures()
+ ->GetFeature("nativeMessaging")
+ ->IsAvailableToContext(context()->extension(),
+ context()->context_type(), context()->url());
+ if (!availability.is_available())
+ return;
+
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (!render_frame)
+ return;
+
+ // The Javascript code should validate/fill the arguments.
+ CHECK(args.Length() >= 2 && args[0]->IsString() && args[1]->IsString());
+
+ std::string extension_id = *v8::String::Utf8Value(args[0]);
+ std::string native_app_name = *v8::String::Utf8Value(args[1]);
+
+ int port_id = -1;
+ render_frame->Send(new ExtensionHostMsg_OpenChannelToNativeApp(
+ render_frame->GetRoutingID(), extension_id, native_app_name, &port_id));
+ args.GetReturnValue().Set(static_cast<int32_t>(port_id));
+}
+
+void RuntimeCustomBindings::GetManifest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(context()->extension());
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ args.GetReturnValue().Set(converter->ToV8Value(
+ context()->extension()->manifest()->value(), context()->v8_context()));
+}
+
+void RuntimeCustomBindings::GetExtensionViews(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 2)
+ return;
+
+ if (!args[0]->IsInt32() || !args[1]->IsString())
+ return;
+
+ // |browser_window_id| == extension_misc::kUnknownWindowId means getting
+ // all views for the current extension.
+ int browser_window_id = args[0]->Int32Value();
+
+ std::string view_type_string =
+ base::ToUpperASCII(*v8::String::Utf8Value(args[1]));
+ // |view_type| == VIEW_TYPE_INVALID means getting any type of
+ // views.
+ ViewType view_type = VIEW_TYPE_INVALID;
+ if (view_type_string == kViewTypeBackgroundPage) {
+ view_type = VIEW_TYPE_EXTENSION_BACKGROUND_PAGE;
+ } else if (view_type_string == kViewTypeTabContents) {
+ view_type = VIEW_TYPE_TAB_CONTENTS;
+ } else if (view_type_string == kViewTypePopup) {
+ view_type = VIEW_TYPE_EXTENSION_POPUP;
+ } else if (view_type_string == kViewTypeExtensionDialog) {
+ view_type = VIEW_TYPE_EXTENSION_DIALOG;
+ } else if (view_type_string == kViewTypeAppWindow) {
+ view_type = VIEW_TYPE_APP_WINDOW;
+ } else if (view_type_string == kViewTypeLauncherPage) {
+ view_type = VIEW_TYPE_LAUNCHER_PAGE;
+ } else if (view_type_string == kViewTypePanel) {
+ view_type = VIEW_TYPE_PANEL;
+ } else if (view_type_string != kViewTypeAll) {
+ return;
+ }
+
+ std::string extension_id = context()->GetExtensionID();
+ if (extension_id.empty())
+ return;
+
+ std::vector<content::RenderFrame*> frames =
+ ExtensionFrameHelper::GetExtensionFrames(extension_id, browser_window_id,
+ view_type);
+ v8::Local<v8::Context> v8_context = args.GetIsolate()->GetCurrentContext();
+ v8::Local<v8::Array> v8_views = v8::Array::New(args.GetIsolate());
+ int v8_index = 0;
+ for (content::RenderFrame* frame : frames) {
+ // We filter out iframes here. GetExtensionViews should only return the
+ // main views, not any subframes. (Returning subframes can cause broken
+ // behavior by treating an app window's iframe as its main frame, and maybe
+ // other nastiness).
+ if (frame->GetWebFrame()->top() != frame->GetWebFrame())
+ continue;
+
+ v8::Local<v8::Context> context =
+ frame->GetWebFrame()->mainWorldScriptContext();
+ if (!context.IsEmpty()) {
+ v8::Local<v8::Value> window = context->Global();
+ DCHECK(!window.IsEmpty());
+ v8::Maybe<bool> maybe =
+ v8_views->CreateDataProperty(v8_context, v8_index++, window);
+ DCHECK(maybe.IsJust() && maybe.FromJust());
+ }
+ }
+
+ args.GetReturnValue().Set(v8_views);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/runtime_custom_bindings.h b/chromium/extensions/renderer/runtime_custom_bindings.h
new file mode 100644
index 00000000000..17e96bbaf79
--- /dev/null
+++ b/chromium/extensions/renderer/runtime_custom_bindings.h
@@ -0,0 +1,34 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_RUNTIME_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_RUNTIME_CUSTOM_BINDINGS_H_
+
+#include "base/compiler_specific.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// The native component of custom bindings for the chrome.runtime API.
+class RuntimeCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit RuntimeCustomBindings(ScriptContext* context);
+
+ ~RuntimeCustomBindings() override;
+
+ // Creates a new messaging channel to the given extension.
+ void OpenChannelToExtension(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Creates a new messaging channels for the specified native application.
+ void OpenChannelToNativeApp(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ private:
+ void GetManifest(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetExtensionViews(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_RUNTIME_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/safe_builtins.cc b/chromium/extensions/renderer/safe_builtins.cc
new file mode 100644
index 00000000000..914f4eaf940
--- /dev/null
+++ b/chromium/extensions/renderer/safe_builtins.cc
@@ -0,0 +1,259 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/safe_builtins.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+namespace {
+
+const char kClassName[] = "extensions::SafeBuiltins";
+
+// Documentation for makeCallback in the JavaScript, out here to reduce the
+// (very small) amount of effort that the v8 parser needs to do:
+//
+// Returns a new object with every function on |obj| configured to call()\n"
+// itself with the given arguments.\n"
+// E.g. given\n"
+// var result = makeCallable(Function.prototype)\n"
+// |result| will be a object including 'bind' such that\n"
+// result.bind(foo, 1, 2, 3);\n"
+// is equivalent to Function.prototype.bind.call(foo, 1, 2, 3), and so on.\n"
+// This is a convenient way to save functions that user scripts may clobber.\n"
+const char kScript[] =
+ "(function() {\n"
+ "'use strict';\n"
+ "native function Apply();\n"
+ "native function Save();\n"
+ "\n"
+ "// Used in the callback implementation, could potentially be clobbered.\n"
+ "function makeCallable(obj, target, isStatic, propertyNames) {\n"
+ " propertyNames.forEach(function(propertyName) {\n"
+ " var property = obj[propertyName];\n"
+ " target[propertyName] = function() {\n"
+ " var recv = obj;\n"
+ " var firstArgIndex = 0;\n"
+ " if (!isStatic) {\n"
+ " if (arguments.length == 0)\n"
+ " throw 'There must be at least one argument, the receiver';\n"
+ " recv = arguments[0];\n"
+ " firstArgIndex = 1;\n"
+ " }\n"
+ " return Apply(\n"
+ " property, recv, arguments, firstArgIndex, arguments.length);\n"
+ " };\n"
+ " });\n"
+ "}\n"
+ "\n"
+ "function saveBuiltin(builtin, protoPropertyNames, staticPropertyNames) {\n"
+ " var safe = function() {\n"
+ " throw 'Safe objects cannot be called nor constructed. ' +\n"
+ " 'Use $Foo.self() or new $Foo.self() instead.';\n"
+ " };\n"
+ " safe.self = builtin;\n"
+ " makeCallable(builtin.prototype, safe, false, protoPropertyNames);\n"
+ " if (staticPropertyNames)\n"
+ " makeCallable(builtin, safe, true, staticPropertyNames);\n"
+ " Save(builtin.name, safe);\n"
+ "}\n"
+ "\n"
+ "// Save only what is needed by the extension modules.\n"
+ "saveBuiltin(Object,\n"
+ " ['hasOwnProperty'],\n"
+ " ['create', 'defineProperty', 'freeze',\n"
+ " 'getOwnPropertyDescriptor', 'getPrototypeOf', 'keys',\n"
+ " 'assign', 'setPrototypeOf']);\n"
+ "saveBuiltin(Function,\n"
+ " ['apply', 'bind', 'call']);\n"
+ "saveBuiltin(Array,\n"
+ " ['concat', 'forEach', 'indexOf', 'join', 'push', 'slice',\n"
+ " 'splice', 'map', 'filter', 'unshift', 'pop', 'reverse'],\n"
+ " ['isArray']);\n"
+ "saveBuiltin(String,\n"
+ " ['indexOf', 'slice', 'split', 'substr', 'toUpperCase',\n"
+ " 'replace']);\n"
+ "// Use exec rather than test to defend against clobbering in the\n"
+ "// presence of ES2015 semantics, which read RegExp.prototype.exec.\n"
+ "saveBuiltin(RegExp,\n"
+ " ['exec']);\n"
+ "saveBuiltin(Error,\n"
+ " [],\n"
+ " ['captureStackTrace']);\n"
+ "\n"
+ "// JSON is trickier because extensions can override toJSON in\n"
+ "// incompatible ways, and we need to prevent that.\n"
+ "var builtinTypes = [\n"
+ " Object, Function, Array, String, Boolean, Number, Date, RegExp\n"
+ "];\n"
+ "var builtinToJSONs = builtinTypes.map(function(t) {\n"
+ " return t.toJSON;\n"
+ "});\n"
+ "var builtinArray = Array;\n"
+ "var builtinJSONStringify = JSON.stringify;\n"
+ "Save('JSON', {\n"
+ " parse: JSON.parse,\n"
+ " stringify: function(obj) {\n"
+ " var savedToJSONs = new builtinArray(builtinTypes.length);\n"
+ " try {\n"
+ " for (var i = 0; i < builtinTypes.length; ++i) {\n"
+ " try {\n"
+ " if (builtinTypes[i].prototype.toJSON !==\n"
+ " builtinToJSONs[i]) {\n"
+ " savedToJSONs[i] = builtinTypes[i].prototype.toJSON;\n"
+ " builtinTypes[i].prototype.toJSON = builtinToJSONs[i];\n"
+ " }\n"
+ " } catch (e) {}\n"
+ " }\n"
+ " } catch (e) {}\n"
+ " try {\n"
+ " return builtinJSONStringify(obj);\n"
+ " } finally {\n"
+ " for (var i = 0; i < builtinTypes.length; ++i) {\n"
+ " try {\n"
+ " if (i in savedToJSONs)\n"
+ " builtinTypes[i].prototype.toJSON = savedToJSONs[i];\n"
+ " } catch (e) {}\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "});\n"
+ "\n"
+ "}());\n";
+
+v8::Local<v8::Private> MakeKey(const char* name, v8::Isolate* isolate) {
+ return v8::Private::ForApi(
+ isolate, ToV8StringUnsafe(
+ isolate, base::StringPrintf("%s::%s", kClassName, name)));
+}
+
+void SaveImpl(const char* name,
+ v8::Local<v8::Value> value,
+ v8::Local<v8::Context> context) {
+ CHECK(!value.IsEmpty() && value->IsObject()) << name;
+ context->Global()
+ ->SetPrivate(context, MakeKey(name, context->GetIsolate()), value)
+ .FromJust();
+}
+
+v8::Local<v8::Object> Load(const char* name, v8::Local<v8::Context> context) {
+ v8::Local<v8::Value> value =
+ context->Global()
+ ->GetPrivate(context, MakeKey(name, context->GetIsolate()))
+ .ToLocalChecked();
+ CHECK(value->IsObject()) << name;
+ return v8::Local<v8::Object>::Cast(value);
+}
+
+class ExtensionImpl : public v8::Extension {
+ public:
+ ExtensionImpl() : v8::Extension(kClassName, kScript) {}
+
+ private:
+ v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
+ v8::Isolate* isolate,
+ v8::Local<v8::String> name) override {
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+ if (IsTrue(name->Equals(context, ToV8StringUnsafe(isolate, "Apply"))))
+ return v8::FunctionTemplate::New(isolate, Apply);
+ if (IsTrue(name->Equals(context, ToV8StringUnsafe(isolate, "Save"))))
+ return v8::FunctionTemplate::New(isolate, Save);
+ NOTREACHED() << *v8::String::Utf8Value(name);
+ return v8::Local<v8::FunctionTemplate>();
+ }
+
+ static void Apply(const v8::FunctionCallbackInfo<v8::Value>& info) {
+ CHECK(info.Length() == 5 && info[0]->IsFunction() && // function
+ // info[1] could be an object or a string
+ info[2]->IsObject() && // args
+ info[3]->IsInt32() && // first_arg_index
+ info[4]->IsInt32()); // args_length
+ v8::Local<v8::Function> function = info[0].As<v8::Function>();
+ v8::Local<v8::Object> recv;
+ if (info[1]->IsObject()) {
+ recv = v8::Local<v8::Object>::Cast(info[1]);
+ } else if (info[1]->IsString()) {
+ recv = v8::StringObject::New(v8::Local<v8::String>::Cast(info[1]))
+ .As<v8::Object>();
+ } else {
+ info.GetIsolate()->ThrowException(
+ v8::Exception::TypeError(ToV8StringUnsafe(
+ info.GetIsolate(),
+ "The first argument is the receiver and must be an object")));
+ return;
+ }
+ v8::Local<v8::Object> args = v8::Local<v8::Object>::Cast(info[2]);
+ int first_arg_index = info[3].As<v8::Int32>()->Value();
+ int args_length = info[4].As<v8::Int32>()->Value();
+
+ v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
+ int argc = args_length - first_arg_index;
+ scoped_ptr<v8::Local<v8::Value> []> argv(new v8::Local<v8::Value>[argc]);
+ for (int i = 0; i < argc; ++i) {
+ CHECK(IsTrue(args->Has(context, i + first_arg_index)));
+ // Getting a property value could throw an exception.
+ if (!GetProperty(context, args, i + first_arg_index, &argv[i]))
+ return;
+ }
+
+ v8::MicrotasksScope microtasks(
+ info.GetIsolate(), v8::MicrotasksScope::kDoNotRunMicrotasks);
+ v8::Local<v8::Value> return_value;
+ if (function->Call(context, recv, argc, argv.get()).ToLocal(&return_value))
+ info.GetReturnValue().Set(return_value);
+ }
+
+ static void Save(const v8::FunctionCallbackInfo<v8::Value>& info) {
+ CHECK(info.Length() == 2 && info[0]->IsString() && info[1]->IsObject());
+ SaveImpl(*v8::String::Utf8Value(info[0]),
+ info[1],
+ info.GetIsolate()->GetCurrentContext());
+ }
+};
+
+} // namespace
+
+// static
+v8::Extension* SafeBuiltins::CreateV8Extension() { return new ExtensionImpl(); }
+
+SafeBuiltins::SafeBuiltins(ScriptContext* context) : context_(context) {}
+
+SafeBuiltins::~SafeBuiltins() {}
+
+v8::Local<v8::Object> SafeBuiltins::GetArray() const {
+ return Load("Array", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetFunction() const {
+ return Load("Function", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetJSON() const {
+ return Load("JSON", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetObjekt() const {
+ return Load("Object", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetRegExp() const {
+ return Load("RegExp", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetString() const {
+ return Load("String", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetError() const {
+ return Load("Error", context_->v8_context());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/safe_builtins.h b/chromium/extensions/renderer/safe_builtins.h
new file mode 100644
index 00000000000..83aaca9e537
--- /dev/null
+++ b/chromium/extensions/renderer/safe_builtins.h
@@ -0,0 +1,47 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_SAFE_BUILTINS_H_
+#define EXTENSIONS_RENDERER_SAFE_BUILTINS_H_
+
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// A collection of safe builtin objects, in that they won't be tained by
+// extensions overriding methods on them.
+class SafeBuiltins {
+ public:
+ // Creates the v8::Extension which manages SafeBuiltins instances.
+ static v8::Extension* CreateV8Extension();
+
+ explicit SafeBuiltins(ScriptContext* context);
+
+ virtual ~SafeBuiltins();
+
+ // Each method returns an object with methods taken from their respective
+ // builtin object's prototype, adapted to automatically call() themselves.
+ //
+ // Examples:
+ // Array.prototype.forEach.call(...) becomes Array.forEach(...)
+ // Object.prototype.toString.call(...) becomes Object.toString(...)
+ // Object.keys.call(...) becomes Object.keys(...)
+ v8::Local<v8::Object> GetArray() const;
+ v8::Local<v8::Object> GetFunction() const;
+ v8::Local<v8::Object> GetJSON() const;
+ // NOTE(kalman): VS2010 won't compile "GetObject", it mysteriously renames it
+ // to "GetObjectW" - hence GetObjekt. Sorry.
+ v8::Local<v8::Object> GetObjekt() const;
+ v8::Local<v8::Object> GetRegExp() const;
+ v8::Local<v8::Object> GetString() const;
+ v8::Local<v8::Object> GetError() const;
+
+ private:
+ ScriptContext* context_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SAFE_BUILTINS_H_
diff --git a/chromium/extensions/renderer/safe_builtins_unittest.cc b/chromium/extensions/renderer/safe_builtins_unittest.cc
new file mode 100644
index 00000000000..38910a2c5df
--- /dev/null
+++ b/chromium/extensions/renderer/safe_builtins_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/module_system_test.h"
+
+namespace extensions {
+namespace {
+
+class SafeBuiltinsUnittest : public ModuleSystemTest {};
+
+TEST_F(SafeBuiltinsUnittest, TestNotOriginalObject) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');\n"
+ "Array.foo = 10;\n"
+ "assert.AssertTrue(!$Array.hasOwnProperty('foo'));\n");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(SafeBuiltinsUnittest, TestSelf) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');\n"
+ "Array.foo = 10;\n"
+ "assert.AssertTrue($Array.self.foo == 10);\n"
+ "var arr = $Array.self(1);\n"
+ "assert.AssertTrue(arr.length == 1);\n"
+ "assert.AssertTrue(arr[0] === undefined);\n");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(SafeBuiltinsUnittest, TestStaticFunction) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');\n"
+ "Object.keys = function() {throw new Error()};\n"
+ "var obj = {a: 10};\n"
+ "var keys = $Object.keys(obj);\n"
+ "assert.AssertTrue(keys.length == 1);\n"
+ "assert.AssertTrue(keys[0] == 'a');\n");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(SafeBuiltinsUnittest, TestInstanceMethod) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var assert = requireNative('assert');\n"
+ "Array.prototype.push = function() {throw new Error();}\n"
+ "var arr = []\n"
+ "$Array.push(arr, 1);\n"
+ "assert.AssertTrue(arr.length == 1);\n"
+ "assert.AssertTrue(arr[0] == 1);\n");
+ env()->module_system()->Require("test");
+}
+
+// NOTE: JSON is already tested in ExtensionApiTest.Messaging, via
+// chrome/test/data/extensions/api_test/messaging/connect/page.js.
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/scoped_web_frame.cc b/chromium/extensions/renderer/scoped_web_frame.cc
new file mode 100644
index 00000000000..88c4098b5c3
--- /dev/null
+++ b/chromium/extensions/renderer/scoped_web_frame.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/scoped_web_frame.h"
+
+#include "third_party/WebKit/public/web/WebHeap.h"
+
+namespace extensions {
+
+ScopedWebFrame::ScopedWebFrame() : view_(nullptr), frame_(nullptr) {
+ view_ = blink::WebView::create(nullptr);
+ frame_ = blink::WebLocalFrame::create(
+ blink::WebTreeScopeType::Document, nullptr);
+ view_->setMainFrame(frame_);
+}
+
+ScopedWebFrame::~ScopedWebFrame() {
+ view_->close();
+ frame_->close();
+ blink::WebHeap::collectAllGarbageForTesting();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/scoped_web_frame.h b/chromium/extensions/renderer/scoped_web_frame.h
new file mode 100644
index 00000000000..948a289483b
--- /dev/null
+++ b/chromium/extensions/renderer/scoped_web_frame.h
@@ -0,0 +1,34 @@
+// Copyright 2015 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.
+
+#ifndef SCOPED_WEB_FRAME_H_
+#define SCOPED_WEB_FRAME_H_
+
+#include "base/macros.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+namespace extensions {
+
+// ScopedWebFrame is a class to create a dummy webview and frame for testing.
+// The dymmy webview and frame will be destructed when the scope exits.
+class ScopedWebFrame {
+public:
+ ScopedWebFrame();
+ ~ScopedWebFrame();
+
+ blink::WebLocalFrame* frame() { return frame_; }
+
+private:
+ // The webview and the frame are kept alive by the ScopedWebFrame
+ // because they are not destructed unless ~ScopedWebFrame explicitly
+ // closes the webview and the frame.
+ blink::WebView* view_;
+ blink::WebLocalFrame* frame_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedWebFrame);
+};
+
+} // namespace extensions
+
+#endif // SCOPED_WEB_FRAME_H_
diff --git a/chromium/extensions/renderer/script_context.cc b/chromium/extensions/renderer/script_context.cc
new file mode 100644
index 00000000000..a1d8996a5da
--- /dev/null
+++ b/chromium/extensions/renderer/script_context.cc
@@ -0,0 +1,489 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/script_context.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/features/base_feature_provider.h"
+#include "extensions/common/manifest_handlers/sandboxed_page_info.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "gin/per_context_data.h"
+#include "third_party/WebKit/public/platform/WebSecurityOrigin.h"
+#include "third_party/WebKit/public/web/WebDataSource.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "v8/include/v8.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+namespace {
+
+std::string GetContextTypeDescriptionString(Feature::Context context_type) {
+ switch (context_type) {
+ case Feature::UNSPECIFIED_CONTEXT:
+ return "UNSPECIFIED";
+ case Feature::BLESSED_EXTENSION_CONTEXT:
+ return "BLESSED_EXTENSION";
+ case Feature::UNBLESSED_EXTENSION_CONTEXT:
+ return "UNBLESSED_EXTENSION";
+ case Feature::CONTENT_SCRIPT_CONTEXT:
+ return "CONTENT_SCRIPT";
+ case Feature::WEB_PAGE_CONTEXT:
+ return "WEB_PAGE";
+ case Feature::BLESSED_WEB_PAGE_CONTEXT:
+ return "BLESSED_WEB_PAGE";
+ case Feature::WEBUI_CONTEXT:
+ return "WEBUI";
+ case Feature::SERVICE_WORKER_CONTEXT:
+ return "SERVICE_WORKER";
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+static std::string ToStringOrDefault(
+ const v8::Local<v8::String>& v8_string,
+ const std::string& dflt) {
+ if (v8_string.IsEmpty())
+ return dflt;
+ std::string ascii_value = *v8::String::Utf8Value(v8_string);
+ return ascii_value.empty() ? dflt : ascii_value;
+}
+
+} // namespace
+
+// A gin::Runner that delegates to its ScriptContext.
+class ScriptContext::Runner : public gin::Runner {
+ public:
+ explicit Runner(ScriptContext* context);
+
+ // gin::Runner overrides.
+ void Run(const std::string& source,
+ const std::string& resource_name) override;
+ v8::Local<v8::Value> Call(v8::Local<v8::Function> function,
+ v8::Local<v8::Value> receiver,
+ int argc,
+ v8::Local<v8::Value> argv[]) override;
+ gin::ContextHolder* GetContextHolder() override;
+
+ private:
+ ScriptContext* context_;
+};
+
+ScriptContext::ScriptContext(const v8::Local<v8::Context>& v8_context,
+ blink::WebLocalFrame* web_frame,
+ const Extension* extension,
+ Feature::Context context_type,
+ const Extension* effective_extension,
+ Feature::Context effective_context_type)
+ : is_valid_(true),
+ v8_context_(v8_context->GetIsolate(), v8_context),
+ web_frame_(web_frame),
+ extension_(extension),
+ context_type_(context_type),
+ effective_extension_(effective_extension),
+ effective_context_type_(effective_context_type),
+ safe_builtins_(this),
+ isolate_(v8_context->GetIsolate()),
+ url_(web_frame_ ? GetDataSourceURLForFrame(web_frame_) : GURL()),
+ runner_(new Runner(this)) {
+ VLOG(1) << "Created context:\n" << GetDebugString();
+ gin::PerContextData* gin_data = gin::PerContextData::From(v8_context);
+ CHECK(gin_data);
+ gin_data->set_runner(runner_.get());
+}
+
+ScriptContext::~ScriptContext() {
+ VLOG(1) << "Destroyed context for extension\n"
+ << " extension id: " << GetExtensionID() << "\n"
+ << " effective extension id: "
+ << (effective_extension_.get() ? effective_extension_->id() : "");
+ CHECK(!is_valid_) << "ScriptContexts must be invalidated before destruction";
+}
+
+// static
+bool ScriptContext::IsSandboxedPage(const GURL& url) {
+ // TODO(kalman): This is checking the wrong thing. See comment in
+ // HasAccessOrThrowError.
+ if (url.SchemeIs(kExtensionScheme)) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(url.host());
+ if (extension) {
+ return SandboxedPageInfo::IsSandboxedPage(extension, url.path());
+ }
+ }
+ return false;
+}
+
+void ScriptContext::Invalidate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CHECK(is_valid_);
+ is_valid_ = false;
+
+ // TODO(kalman): Make ModuleSystem use AddInvalidationObserver.
+ // Ownership graph is a bit weird here.
+ if (module_system_)
+ module_system_->Invalidate();
+
+ // Swap |invalidate_observers_| to a local variable to clear it, and to make
+ // sure it's not mutated as we iterate.
+ std::vector<base::Closure> observers;
+ observers.swap(invalidate_observers_);
+ for (const base::Closure& observer : observers) {
+ observer.Run();
+ }
+ DCHECK(invalidate_observers_.empty())
+ << "Invalidation observers cannot be added during invalidation";
+
+ runner_.reset();
+ v8_context_.Reset();
+}
+
+void ScriptContext::AddInvalidationObserver(const base::Closure& observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ invalidate_observers_.push_back(observer);
+}
+
+const std::string& ScriptContext::GetExtensionID() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return extension_.get() ? extension_->id() : base::EmptyString();
+}
+
+content::RenderFrame* ScriptContext::GetRenderFrame() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (web_frame_)
+ return content::RenderFrame::FromWebFrame(web_frame_);
+ return NULL;
+}
+
+v8::Local<v8::Value> ScriptContext::CallFunction(
+ const v8::Local<v8::Function>& function,
+ int argc,
+ v8::Local<v8::Value> argv[]) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ v8::EscapableHandleScope handle_scope(isolate());
+ v8::Context::Scope scope(v8_context());
+
+ v8::MicrotasksScope microtasks(
+ isolate(), v8::MicrotasksScope::kDoNotRunMicrotasks);
+ if (!is_valid_) {
+ return handle_scope.Escape(
+ v8::Local<v8::Primitive>(v8::Undefined(isolate())));
+ }
+
+ v8::Local<v8::Object> global = v8_context()->Global();
+ if (!web_frame_)
+ return handle_scope.Escape(function->Call(global, argc, argv));
+ return handle_scope.Escape(
+ v8::Local<v8::Value>(web_frame_->callFunctionEvenIfScriptDisabled(
+ function, global, argc, argv)));
+}
+
+v8::Local<v8::Value> ScriptContext::CallFunction(
+ const v8::Local<v8::Function>& function) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return CallFunction(function, 0, nullptr);
+}
+
+Feature::Availability ScriptContext::GetAvailability(
+ const std::string& api_name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (base::StartsWith(api_name, "test", base::CompareCase::SENSITIVE)) {
+ bool allowed = base::CommandLine::ForCurrentProcess()->
+ HasSwitch(::switches::kTestType);
+ Feature::AvailabilityResult result =
+ allowed ? Feature::IS_AVAILABLE : Feature::MISSING_COMMAND_LINE_SWITCH;
+ return Feature::Availability(result,
+ allowed ? "" : "Only allowed in tests");
+ }
+ // Hack: Hosted apps should have the availability of messaging APIs based on
+ // the URL of the page (which might have access depending on some extension
+ // with externally_connectable), not whether the app has access to messaging
+ // (which it won't).
+ const Extension* extension = extension_.get();
+ if (extension && extension->is_hosted_app() &&
+ (api_name == "runtime.connect" || api_name == "runtime.sendMessage")) {
+ extension = NULL;
+ }
+ return ExtensionAPI::GetSharedInstance()->IsAvailable(api_name, extension,
+ context_type_, url());
+}
+
+void ScriptContext::DispatchEvent(const char* event_name,
+ v8::Local<v8::Array> args) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ v8::HandleScope handle_scope(isolate());
+ v8::Context::Scope context_scope(v8_context());
+
+ v8::Local<v8::Value> argv[] = {v8::String::NewFromUtf8(isolate(), event_name),
+ args};
+ module_system_->CallModuleMethod(
+ kEventBindings, "dispatchEvent", arraysize(argv), argv);
+}
+
+std::string ScriptContext::GetContextTypeDescription() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return GetContextTypeDescriptionString(context_type_);
+}
+
+std::string ScriptContext::GetEffectiveContextTypeDescription() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return GetContextTypeDescriptionString(effective_context_type_);
+}
+
+bool ScriptContext::IsAnyFeatureAvailableToContext(const Feature& api) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return ExtensionAPI::GetSharedInstance()->IsAnyFeatureAvailableToContext(
+ api, extension(), context_type(), GetDataSourceURLForFrame(web_frame()));
+}
+
+// static
+GURL ScriptContext::GetDataSourceURLForFrame(const blink::WebFrame* frame) {
+ // Normally we would use frame->document().url() to determine the document's
+ // URL, but to decide whether to inject a content script, we use the URL from
+ // the data source. This "quirk" helps prevents content scripts from
+ // inadvertently adding DOM elements to the compose iframe in Gmail because
+ // the compose iframe's dataSource URL is about:blank, but the document URL
+ // changes to match the parent document after Gmail document.writes into
+ // it to create the editor.
+ // http://code.google.com/p/chromium/issues/detail?id=86742
+ blink::WebDataSource* data_source = frame->provisionalDataSource()
+ ? frame->provisionalDataSource()
+ : frame->dataSource();
+ return data_source ? GURL(data_source->request().url()) : GURL();
+}
+
+// static
+GURL ScriptContext::GetEffectiveDocumentURL(const blink::WebFrame* frame,
+ const GURL& document_url,
+ bool match_about_blank) {
+ // Common scenario. If |match_about_blank| is false (as is the case in most
+ // extensions), or if the frame is not an about:-page, just return
+ // |document_url| (supposedly the URL of the frame).
+ if (!match_about_blank || !document_url.SchemeIs(url::kAboutScheme))
+ return document_url;
+
+ // Non-sandboxed about:blank and about:srcdoc pages inherit their security
+ // origin from their parent frame/window. So, traverse the frame/window
+ // hierarchy to find the closest non-about:-page and return its URL.
+ const blink::WebFrame* parent = frame;
+ do {
+ if (parent->parent())
+ parent = parent->parent();
+ else if (parent->opener() != parent)
+ parent = parent->opener();
+ else
+ parent = nullptr;
+ } while (parent && !parent->document().isNull() &&
+ GURL(parent->document().url()).SchemeIs(url::kAboutScheme));
+
+ if (parent && !parent->document().isNull()) {
+ // Only return the parent URL if the frame can access it.
+ const blink::WebDocument& parent_document = parent->document();
+ if (frame->document().getSecurityOrigin().canAccess(
+ parent_document.getSecurityOrigin())) {
+ return parent_document.url();
+ }
+ }
+ return document_url;
+}
+
+ScriptContext* ScriptContext::GetContext() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return this;
+}
+
+void ScriptContext::OnResponseReceived(const std::string& name,
+ int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ v8::HandleScope handle_scope(isolate());
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ v8::Local<v8::Value> argv[] = {
+ v8::Integer::New(isolate(), request_id),
+ v8::String::NewFromUtf8(isolate(), name.c_str()),
+ v8::Boolean::New(isolate(), success),
+ converter->ToV8Value(&response,
+ v8::Local<v8::Context>::New(isolate(), v8_context_)),
+ v8::String::NewFromUtf8(isolate(), error.c_str())};
+
+ v8::Local<v8::Value> retval = module_system()->CallModuleMethod(
+ "sendRequest", "handleResponse", arraysize(argv), argv);
+
+ // In debug, the js will validate the callback parameters and return a
+ // string if a validation error has occured.
+ DCHECK(retval.IsEmpty() || retval->IsUndefined())
+ << *v8::String::Utf8Value(retval);
+}
+
+bool ScriptContext::HasAPIPermission(APIPermission::ID permission) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (effective_extension_.get()) {
+ return effective_extension_->permissions_data()->HasAPIPermission(
+ permission);
+ }
+ if (context_type() == Feature::WEB_PAGE_CONTEXT) {
+ // Only web page contexts may be granted content capabilities. Other
+ // contexts are either privileged WebUI or extensions with their own set of
+ // permissions.
+ if (content_capabilities_.find(permission) != content_capabilities_.end())
+ return true;
+ }
+ return false;
+}
+
+bool ScriptContext::HasAccessOrThrowError(const std::string& name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // Theoretically[1] we could end up with bindings being injected into
+ // sandboxed frames, for example content scripts. Don't let them execute API
+ // functions.
+ //
+ // In any case, this check is silly. The frame's document's security origin
+ // already tells us if it's sandboxed. The only problem is that until
+ // crbug.com/466373 is fixed, we don't know the security origin up-front and
+ // may not know it here, either.
+ //
+ // [1] citation needed. This ScriptContext should already be in a state that
+ // doesn't allow this, from ScriptContextSet::ClassifyJavaScriptContext.
+ if (extension() &&
+ SandboxedPageInfo::IsSandboxedPage(extension(), url_.path())) {
+ static const char kMessage[] =
+ "%s cannot be used within a sandboxed frame.";
+ std::string error_msg = base::StringPrintf(kMessage, name.c_str());
+ isolate()->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate(), error_msg.c_str())));
+ return false;
+ }
+
+ Feature::Availability availability = GetAvailability(name);
+ if (!availability.is_available()) {
+ isolate()->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate(), availability.message().c_str())));
+ return false;
+ }
+
+ return true;
+}
+
+std::string ScriptContext::GetDebugString() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return base::StringPrintf(
+ " extension id: %s\n"
+ " frame: %p\n"
+ " URL: %s\n"
+ " context_type: %s\n"
+ " effective extension id: %s\n"
+ " effective context type: %s",
+ extension_.get() ? extension_->id().c_str() : "(none)", web_frame_,
+ url_.spec().c_str(), GetContextTypeDescription().c_str(),
+ effective_extension_.get() ? effective_extension_->id().c_str()
+ : "(none)",
+ GetEffectiveContextTypeDescription().c_str());
+}
+
+std::string ScriptContext::GetStackTraceAsString() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ v8::Local<v8::StackTrace> stack_trace =
+ v8::StackTrace::CurrentStackTrace(isolate(), 10);
+ if (stack_trace.IsEmpty() || stack_trace->GetFrameCount() <= 0) {
+ return " <no stack trace>";
+ }
+ std::string result;
+ for (int i = 0; i < stack_trace->GetFrameCount(); ++i) {
+ v8::Local<v8::StackFrame> frame = stack_trace->GetFrame(i);
+ CHECK(!frame.IsEmpty());
+ result += base::StringPrintf(
+ "\n at %s (%s:%d:%d)",
+ ToStringOrDefault(frame->GetFunctionName(), "<anonymous>").c_str(),
+ ToStringOrDefault(frame->GetScriptName(), "<anonymous>").c_str(),
+ frame->GetLineNumber(), frame->GetColumn());
+ }
+ return result;
+}
+
+v8::Local<v8::Value> ScriptContext::RunScript(
+ v8::Local<v8::String> name,
+ v8::Local<v8::String> code,
+ const RunScriptExceptionHandler& exception_handler) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ v8::EscapableHandleScope handle_scope(isolate());
+ v8::Context::Scope context_scope(v8_context());
+
+ // Prepend extensions:: to |name| so that internal code can be differentiated
+ // from external code in stack traces. This has no effect on behaviour.
+ std::string internal_name =
+ base::StringPrintf("extensions::%s", *v8::String::Utf8Value(name));
+
+ if (internal_name.size() >= v8::String::kMaxLength) {
+ NOTREACHED() << "internal_name is too long.";
+ return v8::Undefined(isolate());
+ }
+
+ v8::MicrotasksScope microtasks(
+ isolate(), v8::MicrotasksScope::kDoNotRunMicrotasks);
+ v8::TryCatch try_catch(isolate());
+ try_catch.SetCaptureMessage(true);
+ v8::ScriptOrigin origin(
+ v8_helpers::ToV8StringUnsafe(isolate(), internal_name.c_str()));
+ v8::Local<v8::Script> script;
+ if (!v8::Script::Compile(v8_context(), code, &origin).ToLocal(&script)) {
+ exception_handler.Run(try_catch);
+ return v8::Undefined(isolate());
+ }
+
+ v8::Local<v8::Value> result;
+ if (!script->Run(v8_context()).ToLocal(&result)) {
+ exception_handler.Run(try_catch);
+ return v8::Undefined(isolate());
+ }
+
+ return handle_scope.Escape(result);
+}
+
+ScriptContext::Runner::Runner(ScriptContext* context) : context_(context) {
+}
+
+void ScriptContext::Runner::Run(const std::string& source,
+ const std::string& resource_name) {
+ context_->module_system()->RunString(source, resource_name);
+}
+
+v8::Local<v8::Value> ScriptContext::Runner::Call(
+ v8::Local<v8::Function> function,
+ v8::Local<v8::Value> receiver,
+ int argc,
+ v8::Local<v8::Value> argv[]) {
+ return context_->CallFunction(function, argc, argv);
+}
+
+gin::ContextHolder* ScriptContext::Runner::GetContextHolder() {
+ v8::HandleScope handle_scope(context_->isolate());
+ return gin::PerContextData::From(context_->v8_context())->context_holder();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_context.h b/chromium/extensions/renderer/script_context.h
new file mode 100644
index 00000000000..48c873ce1fc
--- /dev/null
+++ b/chromium/extensions/renderer/script_context.h
@@ -0,0 +1,259 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_CONTEXT_H_
+#define EXTENSIONS_RENDERER_SCRIPT_CONTEXT_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/renderer/module_system.h"
+#include "extensions/renderer/request_sender.h"
+#include "extensions/renderer/safe_builtins.h"
+#include "gin/runner.h"
+#include "url/gurl.h"
+#include "v8/include/v8.h"
+
+namespace blink {
+class WebFrame;
+class WebLocalFrame;
+}
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+class Extension;
+
+// Extensions wrapper for a v8::Context.
+//
+// v8::Contexts can be constructed on any thread, and must only be accessed or
+// destroyed that thread.
+//
+// Note that ScriptContexts bound to worker threads will not have the full
+// functionality as those bound to the main RenderThread.
+class ScriptContext : public RequestSender::Source {
+ public:
+ using RunScriptExceptionHandler = base::Callback<void(const v8::TryCatch&)>;
+
+ ScriptContext(const v8::Local<v8::Context>& context,
+ blink::WebLocalFrame* frame,
+ const Extension* extension,
+ Feature::Context context_type,
+ const Extension* effective_extension,
+ Feature::Context effective_context_type);
+ ~ScriptContext() override;
+
+ // Returns whether |url| from any Extension in |extension_set| is sandboxed,
+ // as declared in each Extension's manifest.
+ // TODO(kalman): Delete this when crbug.com/466373 is fixed.
+ // See comment in HasAccessOrThrowError.
+ static bool IsSandboxedPage(const GURL& url);
+
+ // Clears the WebFrame for this contexts and invalidates the associated
+ // ModuleSystem.
+ void Invalidate();
+
+ // Registers |observer| to be run when this context is invalidated. Closures
+ // are run immediately when Invalidate() is called, not in a message loop.
+ void AddInvalidationObserver(const base::Closure& observer);
+
+ // Returns true if this context is still valid, false if it isn't.
+ // A context becomes invalid via Invalidate().
+ bool is_valid() const { return is_valid_; }
+
+ v8::Local<v8::Context> v8_context() const {
+ return v8::Local<v8::Context>::New(isolate_, v8_context_);
+ }
+
+ const Extension* extension() const { return extension_.get(); }
+
+ const Extension* effective_extension() const {
+ return effective_extension_.get();
+ }
+
+ blink::WebLocalFrame* web_frame() const { return web_frame_; }
+
+ Feature::Context context_type() const { return context_type_; }
+
+ Feature::Context effective_context_type() const {
+ return effective_context_type_;
+ }
+
+ void set_module_system(scoped_ptr<ModuleSystem> module_system) {
+ module_system_ = std::move(module_system);
+ }
+
+ ModuleSystem* module_system() { return module_system_.get(); }
+
+ SafeBuiltins* safe_builtins() { return &safe_builtins_; }
+
+ const SafeBuiltins* safe_builtins() const { return &safe_builtins_; }
+
+ // Returns the ID of the extension associated with this context, or empty
+ // string if there is no such extension.
+ const std::string& GetExtensionID() const;
+
+ // Returns the RenderFrame associated with this context. Can return NULL if
+ // the context is in the process of being destroyed.
+ content::RenderFrame* GetRenderFrame() const;
+
+ // Runs |function| with appropriate scopes. Doesn't catch exceptions, callers
+ // must do that if they want.
+ //
+ // USE THIS METHOD RATHER THAN v8::Function::Call WHEREVER POSSIBLE.
+ v8::Local<v8::Value> CallFunction(const v8::Local<v8::Function>& function,
+ int argc,
+ v8::Local<v8::Value> argv[]) const;
+ v8::Local<v8::Value> CallFunction(
+ const v8::Local<v8::Function>& function) const;
+
+ void DispatchEvent(const char* event_name, v8::Local<v8::Array> args) const;
+
+ // Returns the availability of the API |api_name|.
+ Feature::Availability GetAvailability(const std::string& api_name);
+
+ // Returns a string description of the type of context this is.
+ std::string GetContextTypeDescription() const;
+
+ // Returns a string description of the effective type of context this is.
+ std::string GetEffectiveContextTypeDescription() const;
+
+ v8::Isolate* isolate() const { return isolate_; }
+
+ // Get the URL of this context's web frame.
+ //
+ // TODO(kalman): Remove this and replace with a GetOrigin() call which reads
+ // of WebDocument::getSecurityOrigin():
+ // - The URL can change (e.g. pushState) but the origin cannot. Luckily it
+ // appears as though callers don't make security decisions based on the
+ // result of url() so it's not a problem... yet.
+ // - Origin is the correct check to be making.
+ // - It might let us remove the about:blank resolving?
+ const GURL& url() const { return url_; }
+
+ // Sets the URL of this ScriptContext. Usually this will automatically be set
+ // on construction, unless this isn't constructed with enough information to
+ // determine the URL (e.g. frame was null).
+ // TODO(kalman): Make this a constructor parameter (as an origin).
+ void set_url(const GURL& url) { url_ = url; }
+
+ // Returns whether the API |api| or any part of the API could be
+ // available in this context without taking into account the context's
+ // extension.
+ bool IsAnyFeatureAvailableToContext(const extensions::Feature& api);
+
+ // Utility to get the URL we will match against for a frame. If the frame has
+ // committed, this is the commited URL. Otherwise it is the provisional URL.
+ // The returned URL may be invalid.
+ static GURL GetDataSourceURLForFrame(const blink::WebFrame* frame);
+
+ // Returns the first non-about:-URL in the document hierarchy above and
+ // including |frame|. The document hierarchy is only traversed if
+ // |document_url| is an about:-URL and if |match_about_blank| is true.
+ static GURL GetEffectiveDocumentURL(const blink::WebFrame* frame,
+ const GURL& document_url,
+ bool match_about_blank);
+
+ // RequestSender::Source implementation.
+ ScriptContext* GetContext() override;
+ void OnResponseReceived(const std::string& name,
+ int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) override;
+
+ // Grants a set of content capabilities to this context.
+ void set_content_capabilities(const APIPermissionSet& capabilities) {
+ content_capabilities_ = capabilities;
+ }
+
+ // Indicates if this context has an effective API permission either by being
+ // a context for an extension which has that permission, or by being a web
+ // context which has been granted the corresponding capability by an
+ // extension.
+ bool HasAPIPermission(APIPermission::ID permission) const;
+
+ // Throws an Error in this context's JavaScript context, if this context does
+ // not have access to |name|. Returns true if this context has access (i.e.
+ // no exception thrown), false if it does not (i.e. an exception was thrown).
+ bool HasAccessOrThrowError(const std::string& name);
+
+ // Returns a string representation of this ScriptContext, for debugging.
+ std::string GetDebugString() const;
+
+ // Gets the current stack trace as a multi-line string to be logged.
+ std::string GetStackTraceAsString() const;
+
+ // Runs |code|, labelling the script that gets created as |name| (the name is
+ // used in the devtools and stack traces). |exception_handler| will be called
+ // re-entrantly if an exception is thrown during the script's execution.
+ v8::Local<v8::Value> RunScript(
+ v8::Local<v8::String> name,
+ v8::Local<v8::String> code,
+ const RunScriptExceptionHandler& exception_handler);
+
+ private:
+ class Runner;
+
+ // Whether this context is valid.
+ bool is_valid_;
+
+ // The v8 context the bindings are accessible to.
+ v8::Global<v8::Context> v8_context_;
+
+ // The WebLocalFrame associated with this context. This can be NULL because
+ // this object can outlive is destroyed asynchronously.
+ blink::WebLocalFrame* web_frame_;
+
+ // The extension associated with this context, or NULL if there is none. This
+ // might be a hosted app in the case that this context is hosting a web URL.
+ scoped_refptr<const Extension> extension_;
+
+ // The type of context.
+ Feature::Context context_type_;
+
+ // The effective extension associated with this context, or NULL if there is
+ // none. This is different from the above extension if this context is in an
+ // about:blank iframe for example.
+ scoped_refptr<const Extension> effective_extension_;
+
+ // The type of context.
+ Feature::Context effective_context_type_;
+
+ // Owns and structures the JS that is injected to set up extension bindings.
+ scoped_ptr<ModuleSystem> module_system_;
+
+ // Contains safe copies of builtin objects like Function.prototype.
+ SafeBuiltins safe_builtins_;
+
+ // The set of capabilities granted to this context by extensions.
+ APIPermissionSet content_capabilities_;
+
+ // A list of base::Closure instances as an observer interface for
+ // invalidation.
+ std::vector<base::Closure> invalidate_observers_;
+
+ v8::Isolate* isolate_;
+
+ GURL url_;
+
+ scoped_ptr<Runner> runner_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptContext);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_CONTEXT_H_
diff --git a/chromium/extensions/renderer/script_context_browsertest.cc b/chromium/extensions/renderer/script_context_browsertest.cc
new file mode 100644
index 00000000000..2559a692910
--- /dev/null
+++ b/chromium/extensions/renderer/script_context_browsertest.cc
@@ -0,0 +1,91 @@
+// Copyright 2014 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.
+
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/test/frame_load_waiter.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "url/gurl.h"
+
+using blink::WebFrame;
+
+namespace extensions {
+namespace {
+
+class ScriptContextTest : public ChromeRenderViewTest {
+ protected:
+ GURL GetEffectiveDocumentURL(const WebFrame* frame) {
+ return ScriptContext::GetEffectiveDocumentURL(
+ frame, frame->document().url(), true);
+ }
+};
+
+TEST_F(ScriptContextTest, GetEffectiveDocumentURL) {
+ GURL top_url("http://example.com/");
+ GURL different_url("http://example.net/");
+ GURL blank_url("about:blank");
+ GURL srcdoc_url("about:srcdoc");
+
+ const char frame_html[] =
+ "<iframe name='frame1' srcdoc=\""
+ " <iframe name='frame1_1'></iframe>"
+ " <iframe name='frame1_2' sandbox=''></iframe>"
+ "\"></iframe>"
+ "<iframe name='frame2' sandbox='' srcdoc=\""
+ " <iframe name='frame2_1'></iframe>"
+ "\"></iframe>"
+ "<iframe name='frame3'></iframe>";
+
+ const char frame3_html[] = "<iframe name='frame3_1'></iframe>";
+
+ WebFrame* frame = GetMainFrame();
+ ASSERT_TRUE(frame);
+
+ frame->loadHTMLString(frame_html, top_url);
+ content::FrameLoadWaiter(content::RenderFrame::FromWebFrame(frame)).Wait();
+
+ WebFrame* frame1 = frame->findChildByName("frame1");
+ ASSERT_TRUE(frame1);
+ WebFrame* frame1_1 = frame1->findChildByName("frame1_1");
+ ASSERT_TRUE(frame1_1);
+ WebFrame* frame1_2 = frame1->findChildByName("frame1_2");
+ ASSERT_TRUE(frame1_2);
+ WebFrame* frame2 = frame->findChildByName("frame2");
+ ASSERT_TRUE(frame2);
+ WebFrame* frame2_1 = frame2->findChildByName("frame2_1");
+ ASSERT_TRUE(frame2_1);
+ WebFrame* frame3 = frame->findChildByName("frame3");
+ ASSERT_TRUE(frame3);
+
+ // Load a blank document in a frame from a different origin.
+ frame3->loadHTMLString(frame3_html, different_url);
+ content::FrameLoadWaiter(content::RenderFrame::FromWebFrame(frame3)).Wait();
+
+ WebFrame* frame3_1 = frame->findChildByName("frame3");
+ ASSERT_TRUE(frame3_1);
+
+ // Top-level frame
+ EXPECT_EQ(GetEffectiveDocumentURL(frame), top_url);
+ // top -> srcdoc = inherit
+ EXPECT_EQ(GetEffectiveDocumentURL(frame1), top_url);
+ // top -> srcdoc -> about:blank = inherit
+ EXPECT_EQ(GetEffectiveDocumentURL(frame1_1), top_url);
+ // top -> srcdoc -> about:blank sandboxed = same URL
+ EXPECT_EQ(GetEffectiveDocumentURL(frame1_2), blank_url);
+
+ // top -> srcdoc [sandboxed] = same URL
+ EXPECT_EQ(GetEffectiveDocumentURL(frame2), srcdoc_url);
+ // top -> srcdoc [sandboxed] -> about:blank = same URL
+ EXPECT_EQ(GetEffectiveDocumentURL(frame2_1), blank_url);
+
+ // top -> different origin = different origin
+ EXPECT_EQ(GetEffectiveDocumentURL(frame3), different_url);
+ // top -> different origin -> about:blank = inherit
+ EXPECT_EQ(GetEffectiveDocumentURL(frame3_1), different_url);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_context_set.cc b/chromium/extensions/renderer/script_context_set.cc
new file mode 100644
index 00000000000..33df67e58d1
--- /dev/null
+++ b/chromium/extensions/renderer/script_context_set.cc
@@ -0,0 +1,223 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/script_context_set.h"
+
+#include "base/message_loop/message_loop.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension.h"
+#include "extensions/renderer/extension_groups.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_injection.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+namespace {
+// There is only ever one instance of the ScriptContextSet.
+ScriptContextSet* g_context_set = nullptr;
+}
+
+ScriptContextSet::ScriptContextSet(ExtensionIdSet* active_extension_ids)
+ : active_extension_ids_(active_extension_ids) {
+ DCHECK(!g_context_set);
+ g_context_set = this;
+}
+
+ScriptContextSet::~ScriptContextSet() {
+ g_context_set = nullptr;
+}
+
+ScriptContext* ScriptContextSet::Register(
+ blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& v8_context,
+ int extension_group,
+ int world_id) {
+ const Extension* extension =
+ GetExtensionFromFrameAndWorld(frame, world_id, false);
+ const Extension* effective_extension =
+ GetExtensionFromFrameAndWorld(frame, world_id, true);
+
+ GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame);
+ Feature::Context context_type =
+ ClassifyJavaScriptContext(extension, extension_group, frame_url,
+ frame->document().getSecurityOrigin());
+ Feature::Context effective_context_type = ClassifyJavaScriptContext(
+ effective_extension, extension_group,
+ ScriptContext::GetEffectiveDocumentURL(frame, frame_url, true),
+ frame->document().getSecurityOrigin());
+
+ ScriptContext* context =
+ new ScriptContext(v8_context, frame, extension, context_type,
+ effective_extension, effective_context_type);
+ contexts_.insert(context); // takes ownership
+ return context;
+}
+
+void ScriptContextSet::Remove(ScriptContext* context) {
+ if (contexts_.erase(context)) {
+ context->Invalidate();
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, context);
+ }
+}
+
+ScriptContext* ScriptContextSet::GetCurrent() const {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ return isolate->InContext() ? GetByV8Context(isolate->GetCurrentContext())
+ : nullptr;
+}
+
+ScriptContext* ScriptContextSet::GetByV8Context(
+ const v8::Local<v8::Context>& v8_context) const {
+ for (ScriptContext* script_context : contexts_) {
+ if (script_context->v8_context() == v8_context)
+ return script_context;
+ }
+ return nullptr;
+}
+
+ScriptContext* ScriptContextSet::GetContextByObject(
+ const v8::Local<v8::Object>& object) {
+ return GetContextByV8Context(object->CreationContext());
+}
+
+ScriptContext* ScriptContextSet::GetContextByV8Context(
+ const v8::Local<v8::Context>& v8_context) {
+ // g_context_set can be null in unittests.
+ return g_context_set ? g_context_set->GetByV8Context(v8_context) : nullptr;
+}
+
+void ScriptContextSet::ForEach(
+ const std::string& extension_id,
+ content::RenderFrame* render_frame,
+ const base::Callback<void(ScriptContext*)>& callback) const {
+ // We copy the context list, because calling into javascript may modify it
+ // out from under us.
+ std::set<ScriptContext*> contexts_copy = contexts_;
+
+ for (ScriptContext* context : contexts_copy) {
+ // For the same reason as above, contexts may become invalid while we run.
+ if (!context->is_valid())
+ continue;
+
+ if (!extension_id.empty()) {
+ const Extension* extension = context->extension();
+ if (!extension || (extension_id != extension->id()))
+ continue;
+ }
+
+ content::RenderFrame* context_render_frame = context->GetRenderFrame();
+ if (!context_render_frame)
+ continue;
+
+ if (render_frame && render_frame != context_render_frame)
+ continue;
+
+ callback.Run(context);
+ }
+}
+
+std::set<ScriptContext*> ScriptContextSet::OnExtensionUnloaded(
+ const std::string& extension_id) {
+ std::set<ScriptContext*> removed;
+ ForEach(extension_id, base::Bind(&ScriptContextSet::RecordAndRemove,
+ base::Unretained(this), &removed));
+ return removed;
+}
+
+const Extension* ScriptContextSet::GetExtensionFromFrameAndWorld(
+ const blink::WebLocalFrame* frame,
+ int world_id,
+ bool use_effective_url) {
+ std::string extension_id;
+ if (world_id != 0) {
+ // Isolated worlds (content script).
+ extension_id = ScriptInjection::GetHostIdForIsolatedWorld(world_id);
+ } else {
+ // Extension pages (chrome-extension:// URLs).
+ GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame);
+ frame_url = ScriptContext::GetEffectiveDocumentURL(frame, frame_url,
+ use_effective_url);
+ extension_id =
+ RendererExtensionRegistry::Get()->GetExtensionOrAppIDByURL(frame_url);
+ }
+
+ // There are conditions where despite a context being associated with an
+ // extension, no extension actually gets found. Ignore "invalid" because CSP
+ // blocks extension page loading by switching the extension ID to "invalid".
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (!extension && !extension_id.empty() && extension_id != "invalid") {
+ // TODO(kalman): Do something here?
+ }
+ return extension;
+}
+
+Feature::Context ScriptContextSet::ClassifyJavaScriptContext(
+ const Extension* extension,
+ int extension_group,
+ const GURL& url,
+ const blink::WebSecurityOrigin& origin) {
+ // WARNING: This logic must match ProcessMap::GetContextType, as much as
+ // possible.
+
+ DCHECK_GE(extension_group, 0);
+ if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS) {
+ return extension ? // TODO(kalman): when does this happen?
+ Feature::CONTENT_SCRIPT_CONTEXT
+ : Feature::UNSPECIFIED_CONTEXT;
+ }
+
+ // We have an explicit check for sandboxed pages before checking whether the
+ // extension is active in this process because:
+ // 1. Sandboxed pages run in the same process as regular extension pages, so
+ // the extension is considered active.
+ // 2. ScriptContext creation (which triggers bindings injection) happens
+ // before the SecurityContext is updated with the sandbox flags (after
+ // reading the CSP header), so the caller can't check if the context's
+ // security origin is unique yet.
+ if (ScriptContext::IsSandboxedPage(url))
+ return Feature::WEB_PAGE_CONTEXT;
+
+ if (extension && active_extension_ids_->count(extension->id()) > 0) {
+ // |extension| is active in this process, but it could be either a true
+ // extension process or within the extent of a hosted app. In the latter
+ // case this would usually be considered a (blessed) web page context,
+ // unless the extension in question is a component extension, in which case
+ // we cheat and call it blessed.
+ return (extension->is_hosted_app() &&
+ extension->location() != Manifest::COMPONENT)
+ ? Feature::BLESSED_WEB_PAGE_CONTEXT
+ : Feature::BLESSED_EXTENSION_CONTEXT;
+ }
+
+ // TODO(kalman): This isUnique() check is wrong, it should be performed as
+ // part of ScriptContext::IsSandboxedPage().
+ if (!origin.isUnique() &&
+ RendererExtensionRegistry::Get()->ExtensionBindingsAllowed(url)) {
+ if (!extension) // TODO(kalman): when does this happen?
+ return Feature::UNSPECIFIED_CONTEXT;
+ return extension->is_hosted_app() ? Feature::BLESSED_WEB_PAGE_CONTEXT
+ : Feature::UNBLESSED_EXTENSION_CONTEXT;
+ }
+
+ if (!url.is_valid())
+ return Feature::UNSPECIFIED_CONTEXT;
+
+ if (url.SchemeIs(content::kChromeUIScheme))
+ return Feature::WEBUI_CONTEXT;
+
+ return Feature::WEB_PAGE_CONTEXT;
+}
+
+void ScriptContextSet::RecordAndRemove(std::set<ScriptContext*>* removed,
+ ScriptContext* context) {
+ removed->insert(context);
+ Remove(context); // Note: context deletion is deferred to the message loop.
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_context_set.h b/chromium/extensions/renderer/script_context_set.h
new file mode 100644
index 00000000000..355139747a4
--- /dev/null
+++ b/chromium/extensions/renderer/script_context_set.h
@@ -0,0 +1,145 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_CONTEXT_SET_H_
+#define EXTENSIONS_RENDERER_SCRIPT_CONTEXT_SET_H_
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "url/gurl.h"
+#include "v8/include/v8.h"
+
+class GURL;
+
+namespace base {
+class ListValue;
+}
+
+namespace blink {
+class WebLocalFrame;
+class WebSecurityOrigin;
+}
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+class ScriptContext;
+
+// A container of ScriptContexts, responsible for both creating and managing
+// them.
+//
+// Since calling JavaScript within a context can cause any number of contexts
+// to be created or destroyed, this has additional smarts to help with the set
+// changing underneath callers.
+class ScriptContextSet {
+ public:
+ ScriptContextSet(
+ // Set of the IDs of extensions that are active in this process.
+ // Must outlive this. TODO(kalman): Combine this and |extensions|.
+ ExtensionIdSet* active_extension_ids);
+
+ ~ScriptContextSet();
+
+ // Returns the number of contexts being tracked by this set.
+ // This may also include invalid contexts. TODO(kalman): Useful?
+ size_t size() const { return contexts_.size(); }
+
+ // Creates and starts managing a new ScriptContext. Ownership is held.
+ // Returns a weak reference to the new ScriptContext.
+ ScriptContext* Register(blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& v8_context,
+ int extension_group,
+ int world_id);
+
+ // If the specified context is contained in this set, remove it, then delete
+ // it asynchronously. After this call returns the context object will still
+ // be valid, but its frame() pointer will be cleared.
+ void Remove(ScriptContext* context);
+
+ // Gets the ScriptContext corresponding to v8::Context::GetCurrent(), or
+ // NULL if no such context exists.
+ ScriptContext* GetCurrent() const;
+
+ // Gets the ScriptContext corresponding to the specified
+ // v8::Context or NULL if no such context exists.
+ ScriptContext* GetByV8Context(const v8::Local<v8::Context>& context) const;
+ // Static equivalent of the above.
+ static ScriptContext* GetContextByV8Context(
+ const v8::Local<v8::Context>& context);
+
+ // Returns the ScriptContext corresponding to the V8 context that created the
+ // given |object|.
+ static ScriptContext* GetContextByObject(const v8::Local<v8::Object>& object);
+
+ // Synchronously runs |callback| with each ScriptContext that belongs to
+ // |extension_id| in |render_frame|.
+ //
+ // An empty |extension_id| will match all extensions, and a null
+ // |render_frame| will match all render views, but try to use the inline
+ // variants of these methods instead.
+ void ForEach(const std::string& extension_id,
+ content::RenderFrame* render_frame,
+ const base::Callback<void(ScriptContext*)>& callback) const;
+ // ForEach which matches all extensions.
+ void ForEach(content::RenderFrame* render_frame,
+ const base::Callback<void(ScriptContext*)>& callback) const {
+ ForEach(std::string(), render_frame, callback);
+ }
+ // ForEach which matches all render views.
+ void ForEach(const std::string& extension_id,
+ const base::Callback<void(ScriptContext*)>& callback) const {
+ ForEach(extension_id, nullptr, callback);
+ }
+
+ // Cleans up contexts belonging to an unloaded extension.
+ //
+ // Returns the set of ScriptContexts that were removed as a result. These
+ // are safe to interact with until the end of the current event loop, since
+ // they're deleted asynchronously.
+ std::set<ScriptContext*> OnExtensionUnloaded(const std::string& extension_id);
+
+ private:
+ // Finds the extension for the JavaScript context associated with the
+ // specified |frame| and isolated world. If |world_id| is zero, finds the
+ // extension ID associated with the main world's JavaScript context. If the
+ // JavaScript context isn't from an extension, returns empty string.
+ const Extension* GetExtensionFromFrameAndWorld(
+ const blink::WebLocalFrame* frame,
+ int world_id,
+ bool use_effective_url);
+
+ // Returns the Feature::Context type of context for a JavaScript context.
+ Feature::Context ClassifyJavaScriptContext(
+ const Extension* extension,
+ int extension_group,
+ const GURL& url,
+ const blink::WebSecurityOrigin& origin);
+
+ // Helper for OnExtensionUnloaded().
+ void RecordAndRemove(std::set<ScriptContext*>* removed,
+ ScriptContext* context);
+
+ // Weak reference to all installed Extensions that are also active in this
+ // process.
+ ExtensionIdSet* active_extension_ids_;
+
+ // The set of all ScriptContexts we own.
+ std::set<ScriptContext*> contexts_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptContextSet);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_CONTEXT_SET_H_
diff --git a/chromium/extensions/renderer/script_context_set_unittest.cc b/chromium/extensions/renderer/script_context_set_unittest.cc
new file mode 100644
index 00000000000..756c94290f5
--- /dev/null
+++ b/chromium/extensions/renderer/script_context_set_unittest.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 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.
+
+#include <vector>
+
+#include "base/message_loop/message_loop.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/scoped_web_frame.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "gin/public/context_holder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+TEST(ScriptContextSetTest, Lifecycle) {
+ base::MessageLoop loop;
+ ScopedWebFrame web_frame;
+
+ // Do this after construction of the webview, since it may construct the
+ // Isolate.
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> v8_context = v8::Context::New(isolate);
+ v8::Context::Scope context_scope(v8_context);
+ // ScriptContext relies on gin, it just doesn't look like it from here.
+ gin::ContextHolder context_holder(isolate);
+ context_holder.SetContext(v8_context);
+
+ ExtensionIdSet active_extensions;
+ ScriptContextSet context_set(&active_extensions);
+ ScriptContext* context = context_set.Register(
+ web_frame.frame(), v8_context, 0, 0); // no extension group or world ID
+
+ // Context is valid and resembles correctness.
+ EXPECT_TRUE(context->is_valid());
+ EXPECT_EQ(web_frame.frame(), context->web_frame());
+ EXPECT_EQ(v8_context, context->v8_context());
+
+ // Context has been correctly added.
+ EXPECT_EQ(1u, context_set.size());
+ EXPECT_EQ(context, context_set.GetByV8Context(v8_context));
+
+ // Test context is correctly removed.
+ context_set.Remove(context);
+ EXPECT_EQ(0u, context_set.size());
+ EXPECT_EQ(nullptr, context_set.GetByV8Context(v8_context));
+
+ // After removal, the context should be marked for destruction.
+ EXPECT_FALSE(context->is_valid());
+
+ // Run loop to do the actual deletion.
+ loop.RunUntilIdle();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_context_unittest.cc b/chromium/extensions/renderer/script_context_unittest.cc
new file mode 100644
index 00000000000..b0a846c6b66
--- /dev/null
+++ b/chromium/extensions/renderer/script_context_unittest.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/module_system_test.h"
+#include "extensions/renderer/script_context.h"
+#include "gin/per_context_data.h"
+#include "gin/runner.h"
+
+namespace extensions {
+
+using ScriptContextTest = ModuleSystemTest;
+
+TEST_F(ScriptContextTest, GinRunnerLifetime) {
+ ExpectNoAssertionsMade();
+ base::WeakPtr<gin::Runner> weak_runner =
+ gin::PerContextData::From(env()->context()->v8_context())
+ ->runner()
+ ->GetWeakPtr();
+ env()->ShutdownModuleSystem();
+ EXPECT_FALSE(weak_runner);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_injection.cc b/chromium/extensions/renderer/script_injection.cc
new file mode 100644
index 00000000000..70464b646c4
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection.cc
@@ -0,0 +1,319 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/script_injection.h"
+
+#include <map>
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/metrics/histogram.h"
+#include "base/timer/elapsed_timer.h"
+#include "base/values.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/host_id.h"
+#include "extensions/renderer/dom_activity_logger.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/extension_groups.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+#include "extensions/renderer/script_injection_callback.h"
+#include "extensions/renderer/scripts_run_info.h"
+#include "third_party/WebKit/public/platform/WebSecurityOrigin.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebScriptSource.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+using IsolatedWorldMap = std::map<std::string, int>;
+base::LazyInstance<IsolatedWorldMap> g_isolated_worlds =
+ LAZY_INSTANCE_INITIALIZER;
+
+const int64_t kInvalidRequestId = -1;
+
+// The id of the next pending injection.
+int64_t g_next_pending_id = 0;
+
+// Gets the isolated world ID to use for the given |injection_host|
+// in the given |frame|. If no isolated world has been created for that
+// |injection_host| one will be created and initialized.
+int GetIsolatedWorldIdForInstance(const InjectionHost* injection_host,
+ blink::WebLocalFrame* frame) {
+ static int g_next_isolated_world_id =
+ ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
+
+ IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();
+
+ int id = 0;
+ const std::string& key = injection_host->id().id();
+ IsolatedWorldMap::iterator iter = isolated_worlds.find(key);
+ if (iter != isolated_worlds.end()) {
+ id = iter->second;
+ } else {
+ id = g_next_isolated_world_id++;
+ // This map will tend to pile up over time, but realistically, you're never
+ // going to have enough injection hosts for it to matter.
+ isolated_worlds[key] = id;
+ }
+
+ // We need to set the isolated world origin and CSP even if it's not a new
+ // world since these are stored per frame, and we might not have used this
+ // isolated world in this frame before.
+ frame->setIsolatedWorldSecurityOrigin(
+ id, blink::WebSecurityOrigin::create(injection_host->url()));
+ frame->setIsolatedWorldContentSecurityPolicy(
+ id, blink::WebString::fromUTF8(
+ injection_host->GetContentSecurityPolicy()));
+ frame->setIsolatedWorldHumanReadableName(
+ id, blink::WebString::fromUTF8(injection_host->name()));
+
+ return id;
+}
+
+} // namespace
+
+// Watches for the deletion of a RenderFrame, after which is_valid will return
+// false.
+class ScriptInjection::FrameWatcher : public content::RenderFrameObserver {
+ public:
+ FrameWatcher(content::RenderFrame* render_frame,
+ ScriptInjection* injection)
+ : content::RenderFrameObserver(render_frame),
+ injection_(injection) {}
+ ~FrameWatcher() override {}
+
+ private:
+ void FrameDetached() override { injection_->invalidate_render_frame(); }
+ void OnDestruct() override { injection_->invalidate_render_frame(); }
+
+ ScriptInjection* injection_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameWatcher);
+};
+
+// static
+std::string ScriptInjection::GetHostIdForIsolatedWorld(int isolated_world_id) {
+ const IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();
+
+ for (const auto& iter : isolated_worlds) {
+ if (iter.second == isolated_world_id)
+ return iter.first;
+ }
+ return std::string();
+}
+
+// static
+void ScriptInjection::RemoveIsolatedWorld(const std::string& host_id) {
+ g_isolated_worlds.Get().erase(host_id);
+}
+
+ScriptInjection::ScriptInjection(scoped_ptr<ScriptInjector> injector,
+ content::RenderFrame* render_frame,
+ scoped_ptr<const InjectionHost> injection_host,
+ UserScript::RunLocation run_location)
+ : injector_(std::move(injector)),
+ render_frame_(render_frame),
+ injection_host_(std::move(injection_host)),
+ run_location_(run_location),
+ request_id_(kInvalidRequestId),
+ complete_(false),
+ did_inject_js_(false),
+ frame_watcher_(new FrameWatcher(render_frame, this)),
+ weak_ptr_factory_(this) {
+ CHECK(injection_host_.get());
+}
+
+ScriptInjection::~ScriptInjection() {
+ if (!complete_)
+ NotifyWillNotInject(ScriptInjector::WONT_INJECT);
+}
+
+ScriptInjection::InjectionResult ScriptInjection::TryToInject(
+ UserScript::RunLocation current_location,
+ ScriptsRunInfo* scripts_run_info,
+ const CompletionCallback& async_completion_callback) {
+ if (current_location < run_location_)
+ return INJECTION_WAITING; // Wait for the right location.
+
+ if (request_id_ != kInvalidRequestId) {
+ // We're waiting for permission right now, try again later.
+ return INJECTION_WAITING;
+ }
+
+ if (!injection_host_) {
+ NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
+ return INJECTION_FINISHED; // We're done.
+ }
+
+ blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame();
+ switch (injector_->CanExecuteOnFrame(
+ injection_host_.get(), web_frame,
+ ExtensionFrameHelper::Get(render_frame_)->tab_id())) {
+ case PermissionsData::ACCESS_DENIED:
+ NotifyWillNotInject(ScriptInjector::NOT_ALLOWED);
+ return INJECTION_FINISHED; // We're done.
+ case PermissionsData::ACCESS_WITHHELD:
+ RequestPermissionFromBrowser();
+ return INJECTION_WAITING; // Wait around for permission.
+ case PermissionsData::ACCESS_ALLOWED:
+ InjectionResult result = Inject(scripts_run_info);
+ // If the injection is blocked, we need to set the manager so we can
+ // notify it upon completion.
+ if (result == INJECTION_BLOCKED)
+ async_completion_callback_ = async_completion_callback;
+ return result;
+ }
+
+ NOTREACHED();
+ return INJECTION_FINISHED;
+}
+
+ScriptInjection::InjectionResult ScriptInjection::OnPermissionGranted(
+ ScriptsRunInfo* scripts_run_info) {
+ if (!injection_host_) {
+ NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
+ return INJECTION_FINISHED;
+ }
+
+ return Inject(scripts_run_info);
+}
+
+void ScriptInjection::OnHostRemoved() {
+ injection_host_.reset(nullptr);
+}
+
+void ScriptInjection::RequestPermissionFromBrowser() {
+ // If we are just notifying the browser of the injection, then send an
+ // invalid request (which is treated like a notification).
+ request_id_ = g_next_pending_id++;
+ render_frame_->Send(new ExtensionHostMsg_RequestScriptInjectionPermission(
+ render_frame_->GetRoutingID(), host_id().id(), injector_->script_type(),
+ run_location_, request_id_));
+}
+
+void ScriptInjection::NotifyWillNotInject(
+ ScriptInjector::InjectFailureReason reason) {
+ complete_ = true;
+ injector_->OnWillNotInject(reason, render_frame_);
+}
+
+ScriptInjection::InjectionResult ScriptInjection::Inject(
+ ScriptsRunInfo* scripts_run_info) {
+ DCHECK(injection_host_);
+ DCHECK(scripts_run_info);
+ DCHECK(!complete_);
+
+ bool should_inject_js = injector_->ShouldInjectJs(run_location_);
+ bool should_inject_css = injector_->ShouldInjectCss(run_location_);
+ DCHECK(should_inject_js || should_inject_css);
+
+ if (should_inject_js)
+ InjectJs();
+ if (should_inject_css)
+ InjectCss();
+
+ complete_ = did_inject_js_ || !should_inject_js;
+
+ injector_->GetRunInfo(scripts_run_info, run_location_);
+
+ if (complete_) {
+ injector_->OnInjectionComplete(std::move(execution_result_), run_location_,
+ render_frame_);
+ } else {
+ ++scripts_run_info->num_blocking_js;
+ }
+
+ return complete_ ? INJECTION_FINISHED : INJECTION_BLOCKED;
+}
+
+void ScriptInjection::InjectJs() {
+ DCHECK(!did_inject_js_);
+ blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame();
+ std::vector<blink::WebScriptSource> sources =
+ injector_->GetJsSources(run_location_);
+ bool in_main_world = injector_->ShouldExecuteInMainWorld();
+ int world_id = in_main_world
+ ? DOMActivityLogger::kMainWorldId
+ : GetIsolatedWorldIdForInstance(injection_host_.get(),
+ web_frame);
+ bool is_user_gesture = injector_->IsUserGesture();
+
+ scoped_ptr<blink::WebScriptExecutionCallback> callback(
+ new ScriptInjectionCallback(
+ base::Bind(&ScriptInjection::OnJsInjectionCompleted,
+ weak_ptr_factory_.GetWeakPtr())));
+
+ base::ElapsedTimer exec_timer;
+ if (injection_host_->id().type() == HostID::EXTENSIONS)
+ DOMActivityLogger::AttachToWorld(world_id, injection_host_->id().id());
+ if (in_main_world) {
+ // We only inject in the main world for javascript: urls.
+ DCHECK_EQ(1u, sources.size());
+
+ web_frame->requestExecuteScriptAndReturnValue(sources.front(),
+ is_user_gesture,
+ callback.release());
+ } else {
+ web_frame->requestExecuteScriptInIsolatedWorld(
+ world_id,
+ &sources.front(),
+ sources.size(),
+ EXTENSION_GROUP_CONTENT_SCRIPTS,
+ is_user_gesture,
+ callback.release());
+ }
+
+ if (injection_host_->id().type() == HostID::EXTENSIONS)
+ UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
+}
+
+void ScriptInjection::OnJsInjectionCompleted(
+ const blink::WebVector<v8::Local<v8::Value> >& results) {
+ DCHECK(!did_inject_js_);
+
+ bool expects_results = injector_->ExpectsResults();
+ if (expects_results) {
+ if (!results.isEmpty() && !results[0].IsEmpty()) {
+ // Right now, we only support returning single results (per frame).
+ scoped_ptr<content::V8ValueConverter> v8_converter(
+ content::V8ValueConverter::create());
+ // It's safe to always use the main world context when converting
+ // here. V8ValueConverterImpl shouldn't actually care about the
+ // context scope, and it switches to v8::Object's creation context
+ // when encountered.
+ v8::Local<v8::Context> context =
+ render_frame_->GetWebFrame()->mainWorldScriptContext();
+ execution_result_.reset(v8_converter->FromV8Value(results[0], context));
+ }
+ if (!execution_result_.get())
+ execution_result_ = base::Value::CreateNullValue();
+ }
+ did_inject_js_ = true;
+
+ // If |async_completion_callback_| is set, it means the script finished
+ // asynchronously, and we should run it.
+ if (!async_completion_callback_.is_null()) {
+ injector_->OnInjectionComplete(std::move(execution_result_), run_location_,
+ render_frame_);
+ // Warning: this object can be destroyed after this line!
+ async_completion_callback_.Run(this);
+ }
+}
+
+void ScriptInjection::InjectCss() {
+ std::vector<std::string> css_sources =
+ injector_->GetCssSources(run_location_);
+ blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame();
+ for (const std::string& css : css_sources)
+ web_frame->document().insertStyleSheet(blink::WebString::fromUTF8(css));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_injection.h b/chromium/extensions/renderer/script_injection.h
new file mode 100644
index 00000000000..6b3679573c3
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection.h
@@ -0,0 +1,150 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_INJECTION_H_
+#define EXTENSIONS_RENDERER_SCRIPT_INJECTION_H_
+
+#include <stdint.h>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/common/user_script.h"
+#include "extensions/renderer/injection_host.h"
+#include "extensions/renderer/script_injector.h"
+
+struct HostID;
+
+namespace blink {
+template<typename T> class WebVector;
+}
+
+namespace content {
+class RenderFrame;
+}
+
+namespace v8 {
+class Value;
+template <class T> class Local;
+}
+
+namespace extensions {
+struct ScriptsRunInfo;
+
+// A script wrapper which is aware of whether or not it is allowed to execute,
+// and contains the implementation to do so.
+class ScriptInjection {
+ public:
+ enum InjectionResult {
+ INJECTION_FINISHED,
+ INJECTION_BLOCKED,
+ INJECTION_WAITING
+ };
+
+ using CompletionCallback = base::Callback<void(ScriptInjection*)>;
+
+ // Return the id of the injection host associated with the given world.
+ static std::string GetHostIdForIsolatedWorld(int world_id);
+
+ // Remove the isolated world associated with the given injection host.
+ static void RemoveIsolatedWorld(const std::string& host_id);
+
+ ScriptInjection(scoped_ptr<ScriptInjector> injector,
+ content::RenderFrame* render_frame,
+ scoped_ptr<const InjectionHost> injection_host,
+ UserScript::RunLocation run_location);
+ ~ScriptInjection();
+
+ // Try to inject the script at the |current_location|. This returns
+ // INJECTION_FINISHED if injection has injected or will never inject, returns
+ // INJECTION_BLOCKED if injection is running asynchronously and has not
+ // finished yet, returns INJECTION_WAITING if injections is delayed (either
+ // for permission purposes or because |current_location| is not the designated
+ // |run_location_|).
+ // If INJECTION_BLOCKED is returned, |async_completion_callback| will be
+ // called upon completion.
+ InjectionResult TryToInject(
+ UserScript::RunLocation current_location,
+ ScriptsRunInfo* scripts_run_info,
+ const CompletionCallback& async_completion_callback);
+
+ // Called when permission for the given injection has been granted.
+ // Returns INJECTION_FINISHED if injection has injected or will never inject,
+ // returns INJECTION_BLOCKED if injection is ran asynchronously.
+ InjectionResult OnPermissionGranted(ScriptsRunInfo* scripts_run_info);
+
+ // Resets the pointer of the injection host when the host is gone.
+ void OnHostRemoved();
+
+ void invalidate_render_frame() { render_frame_ = nullptr; }
+
+ // Accessors.
+ content::RenderFrame* render_frame() const { return render_frame_; }
+ const HostID& host_id() const { return injection_host_->id(); }
+ int64_t request_id() const { return request_id_; }
+
+ private:
+ class FrameWatcher;
+
+ // Sends a message to the browser to request permission to inject.
+ void RequestPermissionFromBrowser();
+
+ // Injects the script. Returns INJECTION_FINISHED if injection has finished,
+ // otherwise INJECTION_BLOCKED.
+ InjectionResult Inject(ScriptsRunInfo* scripts_run_info);
+
+ // Inject any JS scripts into the frame for the injection.
+ void InjectJs();
+
+ // Called when JS injection for the given frame has been completed.
+ void OnJsInjectionCompleted(
+ const blink::WebVector<v8::Local<v8::Value> >& results);
+
+ // Inject any CSS source into the frame for the injection.
+ void InjectCss();
+
+ // Notify that we will not inject, and mark it as acknowledged.
+ void NotifyWillNotInject(ScriptInjector::InjectFailureReason reason);
+
+ // The injector for this injection.
+ scoped_ptr<ScriptInjector> injector_;
+
+ // The RenderFrame into which this should inject the script.
+ content::RenderFrame* render_frame_;
+
+ // The associated injection host.
+ scoped_ptr<const InjectionHost> injection_host_;
+
+ // The location in the document load at which we inject the script.
+ UserScript::RunLocation run_location_;
+
+ // This injection's request id. This will be -1 unless the injection is
+ // currently waiting on permission.
+ int64_t request_id_;
+
+ // Whether or not the injection is complete, either via injecting the script
+ // or because it will never complete.
+ bool complete_;
+
+ // Whether or not the injection successfully injected JS.
+ bool did_inject_js_;
+
+ // Results storage.
+ scoped_ptr<base::Value> execution_result_;
+
+ // The callback to run upon completing asynchronously.
+ CompletionCallback async_completion_callback_;
+
+ // A helper class to hold the render frame and watch for its deletion.
+ scoped_ptr<FrameWatcher> frame_watcher_;
+
+ base::WeakPtrFactory<ScriptInjection> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptInjection);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_INJECTION_H_
diff --git a/chromium/extensions/renderer/script_injection_callback.cc b/chromium/extensions/renderer/script_injection_callback.cc
new file mode 100644
index 00000000000..8b911d1c28c
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection_callback.cc
@@ -0,0 +1,23 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/script_injection_callback.h"
+
+namespace extensions {
+
+ScriptInjectionCallback::ScriptInjectionCallback(
+ const CompleteCallback& injection_completed_callback)
+ : injection_completed_callback_(injection_completed_callback) {
+}
+
+ScriptInjectionCallback::~ScriptInjectionCallback() {
+}
+
+void ScriptInjectionCallback::completed(
+ const blink::WebVector<v8::Local<v8::Value> >& result) {
+ injection_completed_callback_.Run(result);
+ delete this;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_injection_callback.h b/chromium/extensions/renderer/script_injection_callback.h
new file mode 100644
index 00000000000..14819277cbd
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection_callback.h
@@ -0,0 +1,42 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_INJECTION_CALLBACK_H_
+#define EXTENSIONS_RENDERER_SCRIPT_INJECTION_CALLBACK_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "third_party/WebKit/public/web/WebScriptExecutionCallback.h"
+#include "v8/include/v8.h"
+
+namespace blink {
+template<typename T> class WebVector;
+}
+
+namespace extensions {
+
+// A wrapper around a callback to notify a script injection when injection
+// completes.
+// This class manages its own lifetime.
+class ScriptInjectionCallback : public blink::WebScriptExecutionCallback {
+ public:
+ using CompleteCallback =
+ base::Callback<void(
+ const blink::WebVector<v8::Local<v8::Value>>& result)>;
+
+ ScriptInjectionCallback(const CompleteCallback& injection_completed_callback);
+ ~ScriptInjectionCallback() override;
+
+ void completed(
+ const blink::WebVector<v8::Local<v8::Value> >& result) override;
+
+ private:
+ CompleteCallback injection_completed_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptInjectionCallback);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_INJECTION_CALLBACK_H_
diff --git a/chromium/extensions/renderer/script_injection_manager.cc b/chromium/extensions/renderer/script_injection_manager.cc
new file mode 100644
index 00000000000..0e234d0906b
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection_manager.cc
@@ -0,0 +1,514 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/script_injection_manager.h"
+
+#include <utility>
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/extension_injection_host.h"
+#include "extensions/renderer/programmatic_script_injector.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/scripts_run_info.h"
+#include "extensions/renderer/web_ui_injection_host.h"
+#include "ipc/ipc_message_macros.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+// The length of time to wait after the DOM is complete to try and run user
+// scripts.
+const int kScriptIdleTimeoutInMs = 200;
+
+// Returns the RunLocation that follows |run_location|.
+UserScript::RunLocation NextRunLocation(UserScript::RunLocation run_location) {
+ switch (run_location) {
+ case UserScript::DOCUMENT_START:
+ return UserScript::DOCUMENT_END;
+ case UserScript::DOCUMENT_END:
+ return UserScript::DOCUMENT_IDLE;
+ case UserScript::DOCUMENT_IDLE:
+ return UserScript::RUN_LOCATION_LAST;
+ case UserScript::UNDEFINED:
+ case UserScript::RUN_DEFERRED:
+ case UserScript::BROWSER_DRIVEN:
+ case UserScript::RUN_LOCATION_LAST:
+ break;
+ }
+ NOTREACHED();
+ return UserScript::RUN_LOCATION_LAST;
+}
+
+} // namespace
+
+class ScriptInjectionManager::RFOHelper : public content::RenderFrameObserver {
+ public:
+ RFOHelper(content::RenderFrame* render_frame,
+ ScriptInjectionManager* manager);
+ ~RFOHelper() override;
+
+ private:
+ // RenderFrameObserver implementation.
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void DidCreateNewDocument() override;
+ void DidCreateDocumentElement() override;
+ void DidFailProvisionalLoad(const blink::WebURLError& error) override;
+ void DidFinishDocumentLoad() override;
+ void DidFinishLoad() override;
+ void FrameDetached() override;
+ void OnDestruct() override;
+
+ virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
+ virtual void OnExecuteDeclarativeScript(int tab_id,
+ const ExtensionId& extension_id,
+ int script_id,
+ const GURL& url);
+ virtual void OnPermitScriptInjection(int64_t request_id);
+
+ // Tells the ScriptInjectionManager to run tasks associated with
+ // document_idle.
+ void RunIdle();
+
+ void StartInjectScripts(UserScript::RunLocation run_location);
+
+ // Indicate that the frame is no longer valid because it is starting
+ // a new load or closing.
+ void InvalidateAndResetFrame();
+
+ // The owning ScriptInjectionManager.
+ ScriptInjectionManager* manager_;
+
+ bool should_run_idle_;
+
+ base::WeakPtrFactory<RFOHelper> weak_factory_;
+};
+
+ScriptInjectionManager::RFOHelper::RFOHelper(content::RenderFrame* render_frame,
+ ScriptInjectionManager* manager)
+ : content::RenderFrameObserver(render_frame),
+ manager_(manager),
+ should_run_idle_(true),
+ weak_factory_(this) {
+}
+
+ScriptInjectionManager::RFOHelper::~RFOHelper() {
+}
+
+bool ScriptInjectionManager::RFOHelper::OnMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RFOHelper, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection,
+ OnPermitScriptInjection)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript,
+ OnExecuteDeclarativeScript)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ScriptInjectionManager::RFOHelper::DidCreateNewDocument() {
+ // A new document is going to be shown, so invalidate the old document state.
+ // Check that the frame's state is known before invalidating the frame,
+ // because it is possible that a script injection was scheduled before the
+ // page was loaded, e.g. by navigating to a javascript: URL before the page
+ // has loaded.
+ if (manager_->frame_statuses_.count(render_frame()) != 0)
+ InvalidateAndResetFrame();
+}
+
+void ScriptInjectionManager::RFOHelper::DidCreateDocumentElement() {
+ ExtensionFrameHelper::Get(render_frame())
+ ->ScheduleAtDocumentStart(
+ base::Bind(&ScriptInjectionManager::RFOHelper::StartInjectScripts,
+ weak_factory_.GetWeakPtr(), UserScript::DOCUMENT_START));
+}
+
+void ScriptInjectionManager::RFOHelper::DidFailProvisionalLoad(
+ const blink::WebURLError& error) {
+ FrameStatusMap::iterator it = manager_->frame_statuses_.find(render_frame());
+ if (it != manager_->frame_statuses_.end() &&
+ it->second == UserScript::DOCUMENT_START) {
+ // Since the provisional load failed, the frame stays at its previous loaded
+ // state and origin (or the parent's origin for new/about:blank frames).
+ // Reset the frame to DOCUMENT_IDLE in order to reflect that the frame is
+ // done loading, and avoid any deadlock in the system.
+ //
+ // We skip injection of DOCUMENT_END and DOCUMENT_IDLE scripts, because the
+ // injections closely follow the DOMContentLoaded (and onload) events, which
+ // are not triggered after a failed provisional load.
+ // This assumption is verified in the checkDOMContentLoadedEvent subtest of
+ // ExecuteScriptApiTest.FrameWithHttp204 (browser_tests).
+ InvalidateAndResetFrame();
+ should_run_idle_ = false;
+ manager_->frame_statuses_[render_frame()] = UserScript::DOCUMENT_IDLE;
+ }
+}
+
+void ScriptInjectionManager::RFOHelper::DidFinishDocumentLoad() {
+ DCHECK(content::RenderThread::Get());
+ ExtensionFrameHelper::Get(render_frame())
+ ->ScheduleAtDocumentEnd(
+ base::Bind(&ScriptInjectionManager::RFOHelper::StartInjectScripts,
+ weak_factory_.GetWeakPtr(), UserScript::DOCUMENT_END));
+
+ // We try to run idle in two places: here and DidFinishLoad.
+ // DidFinishDocumentLoad() corresponds to completing the document's load,
+ // whereas DidFinishLoad corresponds to completing the document and all
+ // subresources' load. We don't want to hold up script injection for a
+ // particularly slow subresource, so we set a delayed task from here - but if
+ // we finish everything before that point (i.e., DidFinishLoad() is
+ // triggered), then there's no reason to keep waiting.
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ScriptInjectionManager::RFOHelper::RunIdle,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
+}
+
+void ScriptInjectionManager::RFOHelper::DidFinishLoad() {
+ DCHECK(content::RenderThread::Get());
+ // Ensure that we don't block any UI progress by running scripts.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&ScriptInjectionManager::RFOHelper::RunIdle,
+ weak_factory_.GetWeakPtr()));
+}
+
+void ScriptInjectionManager::RFOHelper::FrameDetached() {
+ // The frame is closing - invalidate.
+ InvalidateAndResetFrame();
+}
+
+void ScriptInjectionManager::RFOHelper::OnDestruct() {
+ manager_->RemoveObserver(this);
+}
+
+void ScriptInjectionManager::RFOHelper::OnExecuteCode(
+ const ExtensionMsg_ExecuteCode_Params& params) {
+ manager_->HandleExecuteCode(params, render_frame());
+}
+
+void ScriptInjectionManager::RFOHelper::OnExecuteDeclarativeScript(
+ int tab_id,
+ const ExtensionId& extension_id,
+ int script_id,
+ const GURL& url) {
+ // TODO(markdittmer): URL-checking isn't the best security measure.
+ // Begin script injection workflow only if the current URL is identical to
+ // the one that matched declarative conditions in the browser.
+ if (render_frame()->GetWebFrame()->document().url() == url) {
+ manager_->HandleExecuteDeclarativeScript(render_frame(),
+ tab_id,
+ extension_id,
+ script_id,
+ url);
+ }
+}
+
+void ScriptInjectionManager::RFOHelper::OnPermitScriptInjection(
+ int64_t request_id) {
+ manager_->HandlePermitScriptInjection(request_id);
+}
+
+void ScriptInjectionManager::RFOHelper::RunIdle() {
+ // Only notify the manager if the frame hasn't either been removed or already
+ // had idle run since the task to RunIdle() was posted.
+ if (should_run_idle_) {
+ should_run_idle_ = false;
+ manager_->StartInjectScripts(render_frame(), UserScript::DOCUMENT_IDLE);
+ }
+}
+
+void ScriptInjectionManager::RFOHelper::StartInjectScripts(
+ UserScript::RunLocation run_location) {
+ manager_->StartInjectScripts(render_frame(), run_location);
+}
+
+void ScriptInjectionManager::RFOHelper::InvalidateAndResetFrame() {
+ // Invalidate any pending idle injections, and reset the frame inject on idle.
+ weak_factory_.InvalidateWeakPtrs();
+ // We reset to inject on idle, because the frame can be reused (in the case of
+ // navigation).
+ should_run_idle_ = true;
+ manager_->InvalidateForFrame(render_frame());
+}
+
+ScriptInjectionManager::ScriptInjectionManager(
+ UserScriptSetManager* user_script_set_manager)
+ : user_script_set_manager_(user_script_set_manager),
+ user_script_set_manager_observer_(this) {
+ user_script_set_manager_observer_.Add(user_script_set_manager_);
+}
+
+ScriptInjectionManager::~ScriptInjectionManager() {
+ for (const auto& injection : pending_injections_)
+ injection->invalidate_render_frame();
+ for (const auto& injection : running_injections_)
+ injection->invalidate_render_frame();
+}
+
+void ScriptInjectionManager::OnRenderFrameCreated(
+ content::RenderFrame* render_frame) {
+ rfo_helpers_.push_back(make_scoped_ptr(new RFOHelper(render_frame, this)));
+}
+
+void ScriptInjectionManager::OnExtensionUnloaded(
+ const std::string& extension_id) {
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if ((*iter)->host_id().id() == extension_id) {
+ (*iter)->OnHostRemoved();
+ iter = pending_injections_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+void ScriptInjectionManager::OnInjectionFinished(
+ ScriptInjection* injection) {
+ auto iter =
+ std::find_if(running_injections_.begin(), running_injections_.end(),
+ [injection](const scoped_ptr<ScriptInjection>& mode) {
+ return injection == mode.get();
+ });
+ if (iter != running_injections_.end())
+ running_injections_.erase(iter);
+}
+
+void ScriptInjectionManager::OnUserScriptsUpdated(
+ const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) {
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if (changed_hosts.count((*iter)->host_id()) > 0)
+ iter = pending_injections_.erase(iter);
+ else
+ ++iter;
+ }
+}
+
+void ScriptInjectionManager::RemoveObserver(RFOHelper* helper) {
+ for (auto iter = rfo_helpers_.begin(); iter != rfo_helpers_.end(); ++iter) {
+ if (iter->get() == helper) {
+ rfo_helpers_.erase(iter);
+ break;
+ }
+ }
+}
+
+void ScriptInjectionManager::InvalidateForFrame(content::RenderFrame* frame) {
+ // If the frame invalidated is the frame being injected into, we need to
+ // note it.
+ active_injection_frames_.erase(frame);
+
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if ((*iter)->render_frame() == frame)
+ iter = pending_injections_.erase(iter);
+ else
+ ++iter;
+ }
+
+ frame_statuses_.erase(frame);
+}
+
+void ScriptInjectionManager::StartInjectScripts(
+ content::RenderFrame* frame,
+ UserScript::RunLocation run_location) {
+ FrameStatusMap::iterator iter = frame_statuses_.find(frame);
+ // We also don't execute if we detect that the run location is somehow out of
+ // order. This can happen if:
+ // - The first run location reported for the frame isn't DOCUMENT_START, or
+ // - The run location reported doesn't immediately follow the previous
+ // reported run location.
+ // We don't want to run because extensions may have requirements that scripts
+ // running in an earlier run location have run by the time a later script
+ // runs. Better to just not run.
+ // Note that we check run_location > NextRunLocation() in the second clause
+ // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
+ // can happen multiple times, so we can receive earlier/equal run locations.
+ if ((iter == frame_statuses_.end() &&
+ run_location != UserScript::DOCUMENT_START) ||
+ (iter != frame_statuses_.end() &&
+ run_location > NextRunLocation(iter->second))) {
+ // We also invalidate the frame, because the run order of pending injections
+ // may also be bad.
+ InvalidateForFrame(frame);
+ return;
+ } else if (iter != frame_statuses_.end() && iter->second >= run_location) {
+ // Certain run location signals (like DidCreateDocumentElement) can happen
+ // multiple times. Ignore the subsequent signals.
+ return;
+ }
+
+ // Otherwise, all is right in the world, and we can get on with the
+ // injections!
+ frame_statuses_[frame] = run_location;
+ InjectScripts(frame, run_location);
+}
+
+void ScriptInjectionManager::InjectScripts(
+ content::RenderFrame* frame,
+ UserScript::RunLocation run_location) {
+ // Find any injections that want to run on the given frame.
+ ScriptInjectionVector frame_injections;
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if ((*iter)->render_frame() == frame) {
+ frame_injections.push_back(std::move(*iter));
+ iter = pending_injections_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
+ // Add any injections for user scripts.
+ int tab_id = ExtensionFrameHelper::Get(frame)->tab_id();
+ user_script_set_manager_->GetAllInjections(&frame_injections, frame, tab_id,
+ run_location);
+
+ // Note that we are running in |frame|.
+ active_injection_frames_.insert(frame);
+
+ ScriptsRunInfo scripts_run_info(frame, run_location);
+ for (auto iter = frame_injections.begin(); iter != frame_injections.end();) {
+ // It's possible for the frame to be invalidated in the course of injection
+ // (if a script removes its own frame, for example). If this happens, abort.
+ if (!active_injection_frames_.count(frame))
+ break;
+ scoped_ptr<ScriptInjection> injection(std::move(*iter));
+ iter = frame_injections.erase(iter);
+ TryToInject(std::move(injection), run_location, &scripts_run_info);
+ }
+
+ // We are done running in the frame.
+ active_injection_frames_.erase(frame);
+
+ scripts_run_info.LogRun();
+}
+
+void ScriptInjectionManager::TryToInject(
+ scoped_ptr<ScriptInjection> injection,
+ UserScript::RunLocation run_location,
+ ScriptsRunInfo* scripts_run_info) {
+ // Try to inject the script. If the injection is waiting (i.e., for
+ // permission), add it to the list of pending injections. If the injection
+ // has blocked, add it to the list of running injections.
+ // The Unretained below is safe because this object owns all the
+ // ScriptInjections, so is guaranteed to outlive them.
+ switch (injection->TryToInject(
+ run_location,
+ scripts_run_info,
+ base::Bind(&ScriptInjectionManager::OnInjectionFinished,
+ base::Unretained(this)))) {
+ case ScriptInjection::INJECTION_WAITING:
+ pending_injections_.push_back(std::move(injection));
+ break;
+ case ScriptInjection::INJECTION_BLOCKED:
+ running_injections_.push_back(std::move(injection));
+ break;
+ case ScriptInjection::INJECTION_FINISHED:
+ break;
+ }
+}
+
+void ScriptInjectionManager::HandleExecuteCode(
+ const ExtensionMsg_ExecuteCode_Params& params,
+ content::RenderFrame* render_frame) {
+ scoped_ptr<const InjectionHost> injection_host;
+ if (params.host_id.type() == HostID::EXTENSIONS) {
+ injection_host = ExtensionInjectionHost::Create(params.host_id.id());
+ if (!injection_host)
+ return;
+ } else if (params.host_id.type() == HostID::WEBUI) {
+ injection_host.reset(
+ new WebUIInjectionHost(params.host_id));
+ }
+
+ scoped_ptr<ScriptInjection> injection(new ScriptInjection(
+ scoped_ptr<ScriptInjector>(
+ new ProgrammaticScriptInjector(params, render_frame)),
+ render_frame, std::move(injection_host),
+ static_cast<UserScript::RunLocation>(params.run_at)));
+
+ FrameStatusMap::const_iterator iter = frame_statuses_.find(render_frame);
+ UserScript::RunLocation run_location =
+ iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second;
+
+ ScriptsRunInfo scripts_run_info(render_frame, run_location);
+ TryToInject(std::move(injection), run_location, &scripts_run_info);
+}
+
+void ScriptInjectionManager::HandleExecuteDeclarativeScript(
+ content::RenderFrame* render_frame,
+ int tab_id,
+ const ExtensionId& extension_id,
+ int script_id,
+ const GURL& url) {
+ scoped_ptr<ScriptInjection> injection =
+ user_script_set_manager_->GetInjectionForDeclarativeScript(
+ script_id,
+ render_frame,
+ tab_id,
+ url,
+ extension_id);
+ if (injection.get()) {
+ ScriptsRunInfo scripts_run_info(render_frame, UserScript::BROWSER_DRIVEN);
+ // TODO(markdittmer): Use return value of TryToInject for error handling.
+ TryToInject(std::move(injection), UserScript::BROWSER_DRIVEN,
+ &scripts_run_info);
+
+ scripts_run_info.LogRun();
+ }
+}
+
+void ScriptInjectionManager::HandlePermitScriptInjection(int64_t request_id) {
+ auto iter = pending_injections_.begin();
+ for (; iter != pending_injections_.end(); ++iter) {
+ if ((*iter)->request_id() == request_id) {
+ DCHECK((*iter)->host_id().type() == HostID::EXTENSIONS);
+ break;
+ }
+ }
+ if (iter == pending_injections_.end())
+ return;
+
+ // At this point, because the request is present in pending_injections_, we
+ // know that this is the same page that issued the request (otherwise,
+ // RFOHelper::InvalidateAndResetFrame would have caused it to be cleared out).
+
+ scoped_ptr<ScriptInjection> injection(std::move(*iter));
+ pending_injections_.erase(iter);
+
+ ScriptsRunInfo scripts_run_info(injection->render_frame(),
+ UserScript::RUN_DEFERRED);
+ ScriptInjection::InjectionResult res = injection->OnPermissionGranted(
+ &scripts_run_info);
+ if (res == ScriptInjection::INJECTION_BLOCKED)
+ running_injections_.push_back(std::move(injection));
+ scripts_run_info.LogRun();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_injection_manager.h b/chromium/extensions/renderer/script_injection_manager.h
new file mode 100644
index 00000000000..c0f5da82cce
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection_manager.h
@@ -0,0 +1,126 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_INJECTION_MANAGER_H_
+#define EXTENSIONS_RENDERER_SCRIPT_INJECTION_MANAGER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "extensions/common/user_script.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/user_script_set_manager.h"
+
+struct ExtensionMsg_ExecuteCode_Params;
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+class Extension;
+
+// The ScriptInjectionManager manages extensions injecting scripts into frames
+// via both content/user scripts and tabs.executeScript(). It is responsible for
+// maintaining any pending injections awaiting permission or the appropriate
+// load point, and injecting them when ready.
+class ScriptInjectionManager : public UserScriptSetManager::Observer {
+ public:
+ explicit ScriptInjectionManager(
+ UserScriptSetManager* user_script_set_manager);
+ virtual ~ScriptInjectionManager();
+
+ // Notifies that a new render view has been created.
+ void OnRenderFrameCreated(content::RenderFrame* render_frame);
+
+ // Removes pending injections of the unloaded extension.
+ void OnExtensionUnloaded(const std::string& extension_id);
+
+ private:
+ // A RenderFrameObserver implementation which watches the various render
+ // frames in order to notify the ScriptInjectionManager of different
+ // document load states and IPCs.
+ class RFOHelper;
+
+ using FrameStatusMap =
+ std::map<content::RenderFrame*, UserScript::RunLocation>;
+
+ using ScriptInjectionVector = std::vector<scoped_ptr<ScriptInjection>>;
+
+ // Notifies that an injection has been finished.
+ void OnInjectionFinished(ScriptInjection* injection);
+
+ // UserScriptSetManager::Observer implementation.
+ void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) override;
+
+ // Notifies that an RFOHelper should be removed.
+ void RemoveObserver(RFOHelper* helper);
+
+ // Invalidate any pending tasks associated with |frame|.
+ void InvalidateForFrame(content::RenderFrame* frame);
+
+ // Starts the process to inject appropriate scripts into |frame|.
+ void StartInjectScripts(content::RenderFrame* frame,
+ UserScript::RunLocation run_location);
+
+ // Actually injects the scripts into |frame|.
+ void InjectScripts(content::RenderFrame* frame,
+ UserScript::RunLocation run_location);
+
+ // Try to inject and store injection if it has not finished.
+ void TryToInject(scoped_ptr<ScriptInjection> injection,
+ UserScript::RunLocation run_location,
+ ScriptsRunInfo* scripts_run_info);
+
+ // Handle the ExecuteCode extension message.
+ void HandleExecuteCode(const ExtensionMsg_ExecuteCode_Params& params,
+ content::RenderFrame* render_frame);
+
+ // Handle the ExecuteDeclarativeScript extension message.
+ void HandleExecuteDeclarativeScript(content::RenderFrame* web_frame,
+ int tab_id,
+ const ExtensionId& extension_id,
+ int script_id,
+ const GURL& url);
+
+ // Handle the GrantInjectionPermission extension message.
+ void HandlePermitScriptInjection(int64_t request_id);
+
+ // The map of active web frames to their corresponding statuses. The
+ // RunLocation of the frame corresponds to the last location that has ran.
+ FrameStatusMap frame_statuses_;
+
+ // The frames currently being injected into, so long as that frame is valid.
+ std::set<content::RenderFrame*> active_injection_frames_;
+
+ // The collection of RFOHelpers.
+ std::vector<scoped_ptr<RFOHelper>> rfo_helpers_;
+
+ // The set of UserScripts associated with extensions. Owned by the Dispatcher.
+ UserScriptSetManager* user_script_set_manager_;
+
+ // Pending injections which are waiting for either the proper run location or
+ // user consent.
+ ScriptInjectionVector pending_injections_;
+
+ // Running injections which are waiting for async callbacks from blink.
+ ScriptInjectionVector running_injections_;
+
+ ScopedObserver<UserScriptSetManager, UserScriptSetManager::Observer>
+ user_script_set_manager_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptInjectionManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_INJECTION_MANAGER_H_
diff --git a/chromium/extensions/renderer/script_injector.h b/chromium/extensions/renderer/script_injector.h
new file mode 100644
index 00000000000..a51e1d5a9a0
--- /dev/null
+++ b/chromium/extensions/renderer/script_injector.h
@@ -0,0 +1,98 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_INJECTOR_H_
+#define EXTENSIONS_RENDERER_SCRIPT_INJECTOR_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/user_script.h"
+#include "third_party/WebKit/public/web/WebScriptSource.h"
+
+class GURL;
+class InjectionHost;
+
+namespace blink {
+class WebLocalFrame;
+}
+
+namespace extensions {
+struct ScriptsRunInfo;
+
+// The pseudo-delegate class for a ScriptInjection that provides all necessary
+// information about how to inject the script, including what code to inject,
+// when (run location), and where (world), but without any injection logic.
+class ScriptInjector {
+ public:
+ // The possible reasons for not injecting the script.
+ enum InjectFailureReason {
+ EXTENSION_REMOVED, // The extension was removed before injection.
+ NOT_ALLOWED, // The script is not allowed to inject.
+ WONT_INJECT // The injection won't inject because the user rejected
+ // (or just did not accept) the injection.
+ };
+
+ virtual ~ScriptInjector() {}
+
+ // Returns the script type of this particular injection.
+ virtual UserScript::InjectionType script_type() const = 0;
+
+ // Returns true if the script should execute in the main world.
+ virtual bool ShouldExecuteInMainWorld() const = 0;
+
+ // Returns true if the script is running inside a user gesture.
+ virtual bool IsUserGesture() const = 0;
+
+ // Returns true if the script expects results.
+ virtual bool ExpectsResults() const = 0;
+
+ // Returns true if the script should inject JS source at the given
+ // |run_location|.
+ virtual bool ShouldInjectJs(UserScript::RunLocation run_location) const = 0;
+
+ // Returns true if the script should inject CSS at the given |run_location|.
+ virtual bool ShouldInjectCss(UserScript::RunLocation run_location) const = 0;
+
+ // Returns true if the script should execute on the given |frame|.
+ virtual PermissionsData::AccessType CanExecuteOnFrame(
+ const InjectionHost* injection_host,
+ blink::WebLocalFrame* web_frame,
+ int tab_id) const = 0;
+
+ // Returns the javascript sources to inject at the given |run_location|.
+ // Only called if ShouldInjectJs() is true.
+ virtual std::vector<blink::WebScriptSource> GetJsSources(
+ UserScript::RunLocation run_location) const = 0;
+
+ // Returns the css to inject at the given |run_location|.
+ // Only called if ShouldInjectCss() is true.
+ virtual std::vector<std::string> GetCssSources(
+ UserScript::RunLocation run_location) const = 0;
+
+ // Fill scriptrs run info based on information about injection.
+ virtual void GetRunInfo(
+ ScriptsRunInfo* scripts_run_info,
+ UserScript::RunLocation run_location) const = 0;
+
+ // Notifies the script that injection has completed, with a possibly-populated
+ // list of results (depending on whether or not ExpectsResults() was true).
+ // |render_frame| contains the render frame, or null if the frame was
+ // invalidated.
+ virtual void OnInjectionComplete(
+ scoped_ptr<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) = 0;
+
+ // Notifies the script that injection will never occur.
+ // |render_frame| contains the render frame, or null if the frame was
+ // invalidated.
+ virtual void OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_INJECTOR_H_
diff --git a/chromium/extensions/renderer/scripts_run_info.cc b/chromium/extensions/renderer/scripts_run_info.cc
new file mode 100644
index 00000000000..b31bbe07ba1
--- /dev/null
+++ b/chromium/extensions/renderer/scripts_run_info.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/scripts_run_info.h"
+
+#include "base/metrics/histogram.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+namespace extensions {
+
+ScriptsRunInfo::ScriptsRunInfo(content::RenderFrame* render_frame,
+ UserScript::RunLocation location)
+ : num_css(0u),
+ num_js(0u),
+ num_blocking_js(0u),
+ routing_id_(render_frame->GetRoutingID()),
+ run_location_(location),
+ frame_url_(ScriptContext::GetDataSourceURLForFrame(
+ render_frame->GetWebFrame())) {
+}
+
+ScriptsRunInfo::~ScriptsRunInfo() {
+}
+
+void ScriptsRunInfo::LogRun() {
+ // Notify the browser if any extensions are now executing scripts.
+ if (!executing_scripts.empty()) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_ContentScriptsExecuting(
+ routing_id_, executing_scripts, frame_url_));
+ }
+
+ switch (run_location_) {
+ case UserScript::DOCUMENT_START:
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", num_css);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", num_js);
+ if (num_blocking_js) {
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_BlockingScriptCount",
+ num_blocking_js);
+ } else if (num_css || num_js) {
+ UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", timer.Elapsed());
+ }
+ break;
+ case UserScript::DOCUMENT_END:
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", num_js);
+ if (num_blocking_js) {
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_BlockingScriptCount",
+ num_blocking_js);
+ } else if (num_js) {
+ UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", timer.Elapsed());
+ }
+ break;
+ case UserScript::DOCUMENT_IDLE:
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", num_js);
+ if (num_blocking_js) {
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_BlockingScriptCount",
+ num_blocking_js);
+ } else if (num_js) {
+ UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", timer.Elapsed());
+ }
+ break;
+ case UserScript::RUN_DEFERRED:
+ case UserScript::BROWSER_DRIVEN:
+ // TODO(rdevlin.cronin): Add histograms.
+ break;
+ case UserScript::UNDEFINED:
+ case UserScript::RUN_LOCATION_LAST:
+ NOTREACHED();
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/scripts_run_info.h b/chromium/extensions/renderer/scripts_run_info.h
new file mode 100644
index 00000000000..ebd51dfa43b
--- /dev/null
+++ b/chromium/extensions/renderer/scripts_run_info.h
@@ -0,0 +1,64 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPTS_RUN_INFO_H_
+#define EXTENSIONS_RENDERER_SCRIPTS_RUN_INFO_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/timer/elapsed_timer.h"
+#include "extensions/common/user_script.h"
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+
+// A struct containing information about a script run.
+struct ScriptsRunInfo {
+ // Map of extensions IDs to the executing script paths.
+ typedef std::map<std::string, std::set<std::string> > ExecutingScriptsMap;
+
+ ScriptsRunInfo(content::RenderFrame* render_frame,
+ UserScript::RunLocation location);
+ ~ScriptsRunInfo();
+
+ // The number of CSS scripts injected.
+ size_t num_css;
+ // The number of JS scripts injected.
+ size_t num_js;
+ // The number of blocked JS scripts injected.
+ size_t num_blocking_js;
+ // A map of extension ids to executing script paths.
+ ExecutingScriptsMap executing_scripts;
+ // The elapsed time since the ScriptsRunInfo was constructed.
+ base::ElapsedTimer timer;
+
+ // Log information about a given script run.
+ void LogRun();
+
+ private:
+ // The routinig id to use to notify the browser of any injections. Since the
+ // frame may be deleted in injection, we don't hold on to a reference to it
+ // directly.
+ int routing_id_;
+
+ // The run location at which injection is happening.
+ UserScript::RunLocation run_location_;
+
+ // The url of the frame, preserved for the same reason as the routing id.
+ GURL frame_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptsRunInfo);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPTS_RUN_INFO_H_
diff --git a/chromium/extensions/renderer/send_request_natives.cc b/chromium/extensions/renderer/send_request_natives.cc
new file mode 100644
index 00000000000..ef3e93be9d9
--- /dev/null
+++ b/chromium/extensions/renderer/send_request_natives.cc
@@ -0,0 +1,79 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/send_request_natives.h"
+
+#include <stdint.h>
+
+#include "base/json/json_reader.h"
+#include "content/public/child/v8_value_converter.h"
+#include "extensions/renderer/request_sender.h"
+#include "extensions/renderer/script_context.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+SendRequestNatives::SendRequestNatives(RequestSender* request_sender,
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context), request_sender_(request_sender) {
+ RouteFunction(
+ "StartRequest",
+ base::Bind(&SendRequestNatives::StartRequest, base::Unretained(this)));
+ RouteFunction(
+ "GetGlobal",
+ base::Bind(&SendRequestNatives::GetGlobal, base::Unretained(this)));
+}
+
+// Starts an API request to the browser, with an optional callback. The
+// callback will be dispatched to EventBindings::HandleResponse.
+void SendRequestNatives::StartRequest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(5, args.Length());
+ std::string name = *v8::String::Utf8Value(args[0]);
+ bool has_callback = args[2]->BooleanValue();
+ bool for_io_thread = args[3]->BooleanValue();
+ bool preserve_null_in_objects = args[4]->BooleanValue();
+
+ int request_id = request_sender_->GetNextRequestId();
+ args.GetReturnValue().Set(static_cast<int32_t>(request_id));
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+
+ // See http://crbug.com/149880. The context menus APIs relies on this, but
+ // we shouldn't really be doing it (e.g. for the sake of the storage API).
+ converter->SetFunctionAllowed(true);
+
+ if (!preserve_null_in_objects)
+ converter->SetStripNullFromObjects(true);
+
+ scoped_ptr<base::Value> value_args(
+ converter->FromV8Value(args[1], context()->v8_context()));
+ if (!value_args.get() || !value_args->IsType(base::Value::TYPE_LIST)) {
+ NOTREACHED() << "Unable to convert args passed to StartRequest";
+ return;
+ }
+
+ request_sender_->StartRequest(
+ context(),
+ name,
+ request_id,
+ has_callback,
+ for_io_thread,
+ static_cast<base::ListValue*>(value_args.get()));
+}
+
+void SendRequestNatives::GetGlobal(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsObject());
+ v8::Local<v8::Context> v8_context =
+ v8::Local<v8::Object>::Cast(args[0])->CreationContext();
+ if (ContextCanAccessObject(context()->v8_context(), v8_context->Global(),
+ false)) {
+ args.GetReturnValue().Set(v8_context->Global());
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/send_request_natives.h b/chromium/extensions/renderer/send_request_natives.h
new file mode 100644
index 00000000000..69212e1dbb1
--- /dev/null
+++ b/chromium/extensions/renderer/send_request_natives.h
@@ -0,0 +1,37 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_SEND_REQUEST_NATIVES_H_
+#define EXTENSIONS_RENDERER_SEND_REQUEST_NATIVES_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class RequestSender;
+class ScriptContext;
+
+// Native functions exposed to extensions via JS for calling API functions in
+// the browser.
+class SendRequestNatives : public ObjectBackedNativeHandler {
+ public:
+ SendRequestNatives(RequestSender* request_sender, ScriptContext* context);
+
+ private:
+ // Starts an API request to the browser, with an optional callback. The
+ // callback will be dispatched to EventBindings::HandleResponse.
+ void StartRequest(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Gets a reference to an object's global object.
+ void GetGlobal(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ RequestSender* request_sender_;
+
+ DISALLOW_COPY_AND_ASSIGN(SendRequestNatives);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SEND_REQUEST_NATIVES_H_
diff --git a/chromium/extensions/renderer/set_icon_natives.cc b/chromium/extensions/renderer/set_icon_natives.cc
new file mode 100644
index 00000000000..0df21459cb4
--- /dev/null
+++ b/chromium/extensions/renderer/set_icon_natives.cc
@@ -0,0 +1,155 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/set_icon_natives.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/public/common/common_param_traits.h"
+#include "extensions/renderer/request_sender.h"
+#include "extensions/renderer/script_context.h"
+#include "ipc/ipc_message_utils.h"
+#include "third_party/WebKit/public/web/WebArrayBufferConverter.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace {
+
+const char kInvalidDimensions[] = "ImageData has invalid dimensions.";
+const char kInvalidData[] = "ImageData data length does not match dimensions.";
+const char kNoMemory[] = "Chrome was unable to initialize icon.";
+
+} // namespace
+
+namespace extensions {
+
+SetIconNatives::SetIconNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "SetIconCommon",
+ base::Bind(&SetIconNatives::SetIconCommon, base::Unretained(this)));
+}
+
+bool SetIconNatives::ConvertImageDataToBitmapValue(
+ const v8::Local<v8::Object> image_data,
+ v8::Local<v8::Value>* image_data_bitmap) {
+ v8::Isolate* isolate = context()->v8_context()->GetIsolate();
+ v8::Local<v8::Object> data =
+ image_data->Get(v8::String::NewFromUtf8(isolate, "data"))
+ ->ToObject(isolate);
+ int width =
+ image_data->Get(v8::String::NewFromUtf8(isolate, "width"))->Int32Value();
+ int height =
+ image_data->Get(v8::String::NewFromUtf8(isolate, "height"))->Int32Value();
+
+ if (width <= 0 || height <= 0) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidDimensions)));
+ return false;
+ }
+
+ // We need to be able to safely check |data_length| == 4 * width * height
+ // without overflowing below.
+ int max_width = (std::numeric_limits<int>::max() / 4) / height;
+ if (width > max_width) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidDimensions)));
+ return false;
+ }
+
+ int data_length =
+ data->Get(v8::String::NewFromUtf8(isolate, "length"))->Int32Value();
+ if (data_length != 4 * width * height) {
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidData)));
+ return false;
+ }
+
+ SkBitmap bitmap;
+ if (!bitmap.tryAllocN32Pixels(width, height)) {
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kNoMemory)));
+ return false;
+ }
+ bitmap.eraseARGB(0, 0, 0, 0);
+
+ uint32_t* pixels = bitmap.getAddr32(0, 0);
+ for (int t = 0; t < width * height; t++) {
+ // |data| is RGBA, pixels is ARGB.
+ pixels[t] = SkPreMultiplyColor(
+ ((data->Get(v8::Integer::New(isolate, 4 * t + 3))->Int32Value() & 0xFF)
+ << 24) |
+ ((data->Get(v8::Integer::New(isolate, 4 * t + 0))->Int32Value() & 0xFF)
+ << 16) |
+ ((data->Get(v8::Integer::New(isolate, 4 * t + 1))->Int32Value() & 0xFF)
+ << 8) |
+ ((data->Get(v8::Integer::New(isolate, 4 * t + 2))->Int32Value() & 0xFF)
+ << 0));
+ }
+
+ // Construct the Value object.
+ IPC::Message bitmap_pickle;
+ IPC::WriteParam(&bitmap_pickle, bitmap);
+ blink::WebArrayBuffer buffer =
+ blink::WebArrayBuffer::create(bitmap_pickle.size(), 1);
+ memcpy(buffer.data(), bitmap_pickle.data(), bitmap_pickle.size());
+ *image_data_bitmap = blink::WebArrayBufferConverter::toV8Value(
+ &buffer, context()->v8_context()->Global(), isolate);
+
+ return true;
+}
+
+bool SetIconNatives::ConvertImageDataSetToBitmapValueSet(
+ v8::Local<v8::Object>& details,
+ v8::Local<v8::Object>* bitmap_set_value) {
+ v8::Isolate* isolate = context()->v8_context()->GetIsolate();
+ v8::Local<v8::Object> image_data_set =
+ details->Get(v8::String::NewFromUtf8(isolate, "imageData"))
+ ->ToObject(isolate);
+
+ DCHECK(bitmap_set_value);
+
+ v8::Local<v8::Array> property_names(image_data_set->GetOwnPropertyNames());
+ for (size_t i = 0; i < property_names->Length(); ++i) {
+ v8::Local<v8::Value> key(property_names->Get(i));
+ v8::String::Utf8Value utf8_key(key);
+ int size;
+ if (!base::StringToInt(std::string(*utf8_key), &size))
+ continue;
+ v8::Local<v8::Object> image_data =
+ image_data_set->Get(key)->ToObject(isolate);
+ v8::Local<v8::Value> image_data_bitmap;
+ if (!ConvertImageDataToBitmapValue(image_data, &image_data_bitmap))
+ return false;
+ (*bitmap_set_value)->Set(key, image_data_bitmap);
+ }
+ return true;
+}
+
+void SetIconNatives::SetIconCommon(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsObject());
+ v8::Local<v8::Object> details = args[0]->ToObject(args.GetIsolate());
+ v8::Local<v8::Object> bitmap_set_value(v8::Object::New(args.GetIsolate()));
+ if (!ConvertImageDataSetToBitmapValueSet(details, &bitmap_set_value))
+ return;
+
+ v8::Local<v8::Object> dict(v8::Object::New(args.GetIsolate()));
+ dict->Set(v8::String::NewFromUtf8(args.GetIsolate(), "imageData"),
+ bitmap_set_value);
+ if (details->Has(v8::String::NewFromUtf8(args.GetIsolate(), "tabId"))) {
+ dict->Set(
+ v8::String::NewFromUtf8(args.GetIsolate(), "tabId"),
+ details->Get(v8::String::NewFromUtf8(args.GetIsolate(), "tabId")));
+ }
+ args.GetReturnValue().Set(dict);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/set_icon_natives.h b/chromium/extensions/renderer/set_icon_natives.h
new file mode 100644
index 00000000000..f42589188de
--- /dev/null
+++ b/chromium/extensions/renderer/set_icon_natives.h
@@ -0,0 +1,33 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_SET_ICON_NATIVES_H_
+#define EXTENSIONS_RENDERER_SET_ICON_NATIVES_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Functions exposed to extension JS to implement the setIcon extension API.
+class SetIconNatives : public ObjectBackedNativeHandler {
+ public:
+ explicit SetIconNatives(ScriptContext* context);
+
+ private:
+ bool ConvertImageDataToBitmapValue(const v8::Local<v8::Object> image_data,
+ v8::Local<v8::Value>* image_data_bitmap);
+ bool ConvertImageDataSetToBitmapValueSet(
+ v8::Local<v8::Object>& details,
+ v8::Local<v8::Object>* bitmap_set_value);
+ void SetIconCommon(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(SetIconNatives);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SET_ICON_NATIVES_H_
diff --git a/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.cc b/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.cc
new file mode 100644
index 00000000000..3f54b5ddfb5
--- /dev/null
+++ b/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/static_v8_external_one_byte_string_resource.h"
+
+namespace extensions {
+
+StaticV8ExternalOneByteStringResource::StaticV8ExternalOneByteStringResource(
+ const base::StringPiece& buffer)
+ : buffer_(buffer) {
+}
+
+StaticV8ExternalOneByteStringResource::
+ ~StaticV8ExternalOneByteStringResource() {
+}
+
+const char* StaticV8ExternalOneByteStringResource::data() const {
+ return buffer_.data();
+}
+
+size_t StaticV8ExternalOneByteStringResource::length() const {
+ return buffer_.length();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.h b/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.h
new file mode 100644
index 00000000000..3f569585a7b
--- /dev/null
+++ b/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.h
@@ -0,0 +1,35 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_STATIC_V8_EXTERNAL_ONE_BYTE_STRING_RESOURCE_H_
+#define EXTENSIONS_RENDERER_STATIC_V8_EXTERNAL_ONE_BYTE_STRING_RESOURCE_H_
+
+#include <stddef.h>
+
+#include "base/compiler_specific.h"
+#include "base/strings/string_piece.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// A very simple implementation of v8::ExternalAsciiStringResource that just
+// wraps a buffer. The buffer must outlive the v8 runtime instance this resource
+// is used in.
+class StaticV8ExternalOneByteStringResource
+ : public v8::String::ExternalOneByteStringResource {
+ public:
+ explicit StaticV8ExternalOneByteStringResource(
+ const base::StringPiece& buffer);
+ ~StaticV8ExternalOneByteStringResource() override;
+
+ const char* data() const override;
+ size_t length() const override;
+
+ private:
+ base::StringPiece buffer_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_STATIC_V8_EXTERNAL_ONE_BYTE_STRING_RESOURCE_H_
diff --git a/chromium/extensions/renderer/test_extensions_renderer_client.cc b/chromium/extensions/renderer/test_extensions_renderer_client.cc
new file mode 100644
index 00000000000..5299878ac8a
--- /dev/null
+++ b/chromium/extensions/renderer/test_extensions_renderer_client.cc
@@ -0,0 +1,22 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/test_extensions_renderer_client.h"
+
+namespace extensions {
+
+TestExtensionsRendererClient::TestExtensionsRendererClient() {}
+
+TestExtensionsRendererClient::~TestExtensionsRendererClient() {}
+
+bool TestExtensionsRendererClient::IsIncognitoProcess() const {
+ return false;
+}
+
+int TestExtensionsRendererClient::GetLowestIsolatedWorldId() const {
+ // Note that 0 is reserved for the global world.
+ return 1;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/test_extensions_renderer_client.h b/chromium/extensions/renderer/test_extensions_renderer_client.h
new file mode 100644
index 00000000000..f79c6a95e35
--- /dev/null
+++ b/chromium/extensions/renderer/test_extensions_renderer_client.h
@@ -0,0 +1,28 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_TEST_EXTENSIONS_RENDERER_CLIENT_H_
+#define EXTENSIONS_RENDERER_TEST_EXTENSIONS_RENDERER_CLIENT_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+
+namespace extensions {
+
+class TestExtensionsRendererClient : public ExtensionsRendererClient {
+ public:
+ TestExtensionsRendererClient();
+ ~TestExtensionsRendererClient() override;
+
+ // ExtensionsRendererClient implementation.
+ bool IsIncognitoProcess() const override;
+ int GetLowestIsolatedWorldId() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestExtensionsRendererClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_TEST_EXTENSIONS_RENDERER_CLIENT_H_
diff --git a/chromium/extensions/renderer/test_features_native_handler.cc b/chromium/extensions/renderer/test_features_native_handler.cc
new file mode 100644
index 00000000000..0f228555809
--- /dev/null
+++ b/chromium/extensions/renderer/test_features_native_handler.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/test_features_native_handler.h"
+
+#include "base/bind.h"
+#include "content/public/child/v8_value_converter.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/features/json_feature_provider_source.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+TestFeaturesNativeHandler::TestFeaturesNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("GetAPIFeatures",
+ base::Bind(&TestFeaturesNativeHandler::GetAPIFeatures,
+ base::Unretained(this)));
+}
+
+void TestFeaturesNativeHandler::GetAPIFeatures(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ scoped_ptr<JSONFeatureProviderSource> source(
+ ExtensionsClient::Get()->CreateFeatureProviderSource("api"));
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ args.GetReturnValue().Set(
+ converter->ToV8Value(&source->dictionary(), context()->v8_context()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/test_features_native_handler.h b/chromium/extensions/renderer/test_features_native_handler.h
new file mode 100644
index 00000000000..2422178a1e0
--- /dev/null
+++ b/chromium/extensions/renderer/test_features_native_handler.h
@@ -0,0 +1,22 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_TEST_FEATURES_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_TEST_FEATURES_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class TestFeaturesNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit TestFeaturesNativeHandler(ScriptContext* context);
+
+ private:
+ void GetAPIFeatures(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_TEST_FEATURES_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/test_native_handler.cc b/chromium/extensions/renderer/test_native_handler.cc
new file mode 100644
index 00000000000..31e7dcb9d75
--- /dev/null
+++ b/chromium/extensions/renderer/test_native_handler.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/test_native_handler.h"
+
+#include "extensions/renderer/wake_event_page.h"
+
+namespace extensions {
+
+TestNativeHandler::TestNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetWakeEventPage",
+ base::Bind(&TestNativeHandler::GetWakeEventPage, base::Unretained(this)));
+}
+
+void TestNativeHandler::GetWakeEventPage(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(0, args.Length());
+ args.GetReturnValue().Set(WakeEventPage::Get()->GetForContext(context()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/test_native_handler.h b/chromium/extensions/renderer/test_native_handler.h
new file mode 100644
index 00000000000..88e5d239845
--- /dev/null
+++ b/chromium/extensions/renderer/test_native_handler.h
@@ -0,0 +1,29 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_TEST_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_TEST_NATIVE_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// NativeHandler for the chrome.test API.
+class TestNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit TestNativeHandler(ScriptContext* context);
+
+ private:
+ void GetWakeEventPage(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(TestNativeHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_TEST_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/user_gestures_native_handler.cc b/chromium/extensions/renderer/user_gestures_native_handler.cc
new file mode 100644
index 00000000000..c01a4ae7240
--- /dev/null
+++ b/chromium/extensions/renderer/user_gestures_native_handler.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/user_gestures_native_handler.h"
+
+#include "base/bind.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
+
+namespace extensions {
+
+UserGesturesNativeHandler::UserGesturesNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("IsProcessingUserGesture",
+ "test",
+ base::Bind(&UserGesturesNativeHandler::IsProcessingUserGesture,
+ base::Unretained(this)));
+ RouteFunction("RunWithUserGesture",
+ "test",
+ base::Bind(&UserGesturesNativeHandler::RunWithUserGesture,
+ base::Unretained(this)));
+ RouteFunction("RunWithoutUserGesture",
+ "test",
+ base::Bind(&UserGesturesNativeHandler::RunWithoutUserGesture,
+ base::Unretained(this)));
+}
+
+void UserGesturesNativeHandler::IsProcessingUserGesture(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(v8::Boolean::New(
+ args.GetIsolate(),
+ blink::WebUserGestureIndicator::isProcessingUserGesture()));
+}
+
+void UserGesturesNativeHandler::RunWithUserGesture(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ blink::WebScopedUserGesture user_gesture;
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsFunction());
+ v8::Local<v8::Value> no_args;
+ context()->CallFunction(v8::Local<v8::Function>::Cast(args[0]), 0, &no_args);
+}
+
+void UserGesturesNativeHandler::RunWithoutUserGesture(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ blink::WebUserGestureIndicator::consumeUserGesture();
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsFunction());
+ v8::Local<v8::Value> no_args;
+ context()->CallFunction(v8::Local<v8::Function>::Cast(args[0]), 0, &no_args);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/user_gestures_native_handler.h b/chromium/extensions/renderer/user_gestures_native_handler.h
new file mode 100644
index 00000000000..fbe15fbebc3
--- /dev/null
+++ b/chromium/extensions/renderer/user_gestures_native_handler.h
@@ -0,0 +1,24 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_USER_GESTURES_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_USER_GESTURES_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class UserGesturesNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit UserGesturesNativeHandler(ScriptContext* context);
+
+ private:
+ void IsProcessingUserGesture(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void RunWithUserGesture(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void RunWithoutUserGesture(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_USER_GESTURES_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/user_script_injector.cc b/chromium/extensions/renderer/user_script_injector.cc
new file mode 100644
index 00000000000..9885b4ea8d3
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_injector.cc
@@ -0,0 +1,268 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/user_script_injector.h"
+
+#include <tuple>
+#include <vector>
+
+#include "base/lazy_instance.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/guest_view/extensions_guest_view_messages.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/renderer/injection_host.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/scripts_run_info.h"
+#include "grit/extensions_renderer_resources.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebScriptSource.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+struct RoutingInfoKey {
+ int routing_id;
+ int script_id;
+
+ RoutingInfoKey(int routing_id, int script_id)
+ : routing_id(routing_id), script_id(script_id) {}
+
+ bool operator<(const RoutingInfoKey& other) const {
+ return std::tie(routing_id, script_id) <
+ std::tie(other.routing_id, other.script_id);
+ }
+};
+
+using RoutingInfoMap = std::map<RoutingInfoKey, bool>;
+
+// These two strings are injected before and after the Greasemonkey API and
+// user script to wrap it in an anonymous scope.
+const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
+const char kUserScriptTail[] = "\n})(window);";
+
+// A map records whether a given |script_id| from a webview-added user script
+// is allowed to inject on the render of given |routing_id|.
+// Once a script is added, the decision of whether or not allowed to inject
+// won't be changed.
+// After removed by the webview, the user scipt will also be removed
+// from the render. Therefore, there won't be any query from the same
+// |script_id| and |routing_id| pair.
+base::LazyInstance<RoutingInfoMap> g_routing_info_map =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Greasemonkey API source that is injected with the scripts.
+struct GreasemonkeyApiJsString {
+ GreasemonkeyApiJsString();
+ blink::WebScriptSource GetSource() const;
+
+ private:
+ std::string source_;
+};
+
+// The below constructor, monstrous as it is, just makes a WebScriptSource from
+// the GreasemonkeyApiJs resource.
+GreasemonkeyApiJsString::GreasemonkeyApiJsString()
+ : source_(ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(IDR_GREASEMONKEY_API_JS)
+ .as_string()) {
+}
+
+blink::WebScriptSource GreasemonkeyApiJsString::GetSource() const {
+ return blink::WebScriptSource(blink::WebString::fromUTF8(source_));
+}
+
+base::LazyInstance<GreasemonkeyApiJsString> g_greasemonkey_api =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+UserScriptInjector::UserScriptInjector(const UserScript* script,
+ UserScriptSet* script_list,
+ bool is_declarative)
+ : script_(script),
+ script_id_(script_->id()),
+ host_id_(script_->host_id()),
+ is_declarative_(is_declarative),
+ user_script_set_observer_(this) {
+ user_script_set_observer_.Add(script_list);
+}
+
+UserScriptInjector::~UserScriptInjector() {
+}
+
+void UserScriptInjector::OnUserScriptsUpdated(
+ const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) {
+ // If the host causing this injection changed, then this injection
+ // will be removed, and there's no guarantee the backing script still exists.
+ if (changed_hosts.count(host_id_) > 0)
+ return;
+
+ for (std::vector<UserScript*>::const_iterator iter = scripts.begin();
+ iter != scripts.end();
+ ++iter) {
+ // We need to compare to |script_id_| (and not to script_->id()) because the
+ // old |script_| may be deleted by now.
+ if ((*iter)->id() == script_id_) {
+ script_ = *iter;
+ break;
+ }
+ }
+}
+
+UserScript::InjectionType UserScriptInjector::script_type() const {
+ return UserScript::CONTENT_SCRIPT;
+}
+
+bool UserScriptInjector::ShouldExecuteInMainWorld() const {
+ return false;
+}
+
+bool UserScriptInjector::IsUserGesture() const {
+ return false;
+}
+
+bool UserScriptInjector::ExpectsResults() const {
+ return false;
+}
+
+bool UserScriptInjector::ShouldInjectJs(
+ UserScript::RunLocation run_location) const {
+ return script_->run_location() == run_location &&
+ !script_->js_scripts().empty();
+}
+
+bool UserScriptInjector::ShouldInjectCss(
+ UserScript::RunLocation run_location) const {
+ return run_location == UserScript::DOCUMENT_START &&
+ !script_->css_scripts().empty();
+}
+
+PermissionsData::AccessType UserScriptInjector::CanExecuteOnFrame(
+ const InjectionHost* injection_host,
+ blink::WebLocalFrame* web_frame,
+ int tab_id) const {
+ if (script_->consumer_instance_type() ==
+ UserScript::ConsumerInstanceType::WEBVIEW) {
+ int routing_id = content::RenderView::FromWebView(web_frame->top()->view())
+ ->GetRoutingID();
+
+ RoutingInfoKey key(routing_id, script_->id());
+
+ RoutingInfoMap& map = g_routing_info_map.Get();
+ auto iter = map.find(key);
+
+ bool allowed = false;
+ if (iter != map.end()) {
+ allowed = iter->second;
+ } else {
+ // Send a SYNC IPC message to the browser to check if this is allowed.
+ // This is not ideal, but is mitigated by the fact that this is only done
+ // for webviews, and then only once per host.
+ // TODO(hanxi): Find a more efficient way to do this.
+ content::RenderThread::Get()->Send(
+ new ExtensionsGuestViewHostMsg_CanExecuteContentScriptSync(
+ routing_id, script_->id(), &allowed));
+ map.insert(std::pair<RoutingInfoKey, bool>(key, allowed));
+ }
+
+ return allowed ? PermissionsData::ACCESS_ALLOWED
+ : PermissionsData::ACCESS_DENIED;
+ }
+
+ GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
+ web_frame, web_frame->document().url(), script_->match_about_blank());
+
+ return injection_host->CanExecuteOnFrame(
+ effective_document_url,
+ content::RenderFrame::FromWebFrame(web_frame),
+ tab_id,
+ is_declarative_);
+}
+
+std::vector<blink::WebScriptSource> UserScriptInjector::GetJsSources(
+ UserScript::RunLocation run_location) const {
+ DCHECK_EQ(script_->run_location(), run_location);
+
+ std::vector<blink::WebScriptSource> sources;
+ const UserScript::FileList& js_scripts = script_->js_scripts();
+ bool is_standalone_or_emulate_greasemonkey =
+ script_->is_standalone() || script_->emulate_greasemonkey();
+
+ for (UserScript::FileList::const_iterator iter = js_scripts.begin();
+ iter != js_scripts.end();
+ ++iter) {
+ std::string content = iter->GetContent().as_string();
+
+ // We add this dumb function wrapper for standalone user script to
+ // emulate what Greasemonkey does.
+ // TODO(aa): I think that maybe "is_standalone" scripts don't exist
+ // anymore. Investigate.
+ if (is_standalone_or_emulate_greasemonkey) {
+ content.insert(0, kUserScriptHead);
+ content += kUserScriptTail;
+ }
+ sources.push_back(blink::WebScriptSource(
+ blink::WebString::fromUTF8(content), iter->url()));
+ }
+
+ // Emulate Greasemonkey API for scripts that were converted to extensions
+ // and "standalone" user scripts.
+ if (is_standalone_or_emulate_greasemonkey)
+ sources.insert(sources.begin(), g_greasemonkey_api.Get().GetSource());
+
+ return sources;
+}
+
+std::vector<std::string> UserScriptInjector::GetCssSources(
+ UserScript::RunLocation run_location) const {
+ DCHECK_EQ(UserScript::DOCUMENT_START, run_location);
+
+ std::vector<std::string> sources;
+ const UserScript::FileList& css_scripts = script_->css_scripts();
+ for (UserScript::FileList::const_iterator iter = css_scripts.begin();
+ iter != css_scripts.end();
+ ++iter) {
+ sources.push_back(iter->GetContent().as_string());
+ }
+ return sources;
+}
+
+void UserScriptInjector::GetRunInfo(
+ ScriptsRunInfo* scripts_run_info,
+ UserScript::RunLocation run_location) const {
+ if (ShouldInjectJs(run_location)) {
+ const UserScript::FileList& js_scripts = script_->js_scripts();
+ scripts_run_info->num_js += js_scripts.size();
+ for (UserScript::FileList::const_iterator iter = js_scripts.begin();
+ iter != js_scripts.end();
+ ++iter) {
+ scripts_run_info->executing_scripts[host_id_.id()].insert(
+ iter->url().path());
+ }
+ }
+
+ if (ShouldInjectCss(run_location))
+ scripts_run_info->num_css += script_->css_scripts().size();
+}
+
+void UserScriptInjector::OnInjectionComplete(
+ scoped_ptr<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) {
+}
+
+void UserScriptInjector::OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/user_script_injector.h b/chromium/extensions/renderer/user_script_injector.h
new file mode 100644
index 00000000000..61909894e31
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_injector.h
@@ -0,0 +1,86 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_USER_SCRIPT_INJECTOR_H_
+#define EXTENSIONS_RENDERER_USER_SCRIPT_INJECTOR_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "extensions/common/user_script.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/user_script_set.h"
+
+class InjectionHost;
+
+namespace blink {
+class WebLocalFrame;
+}
+
+namespace extensions {
+
+// A ScriptInjector for UserScripts.
+class UserScriptInjector : public ScriptInjector,
+ public UserScriptSet::Observer {
+ public:
+ UserScriptInjector(const UserScript* user_script,
+ UserScriptSet* user_script_set,
+ bool is_declarative);
+ ~UserScriptInjector() override;
+
+ private:
+ // UserScriptSet::Observer implementation.
+ void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) override;
+
+ // ScriptInjector implementation.
+ UserScript::InjectionType script_type() const override;
+ bool ShouldExecuteInMainWorld() const override;
+ bool IsUserGesture() const override;
+ bool ExpectsResults() const override;
+ bool ShouldInjectJs(UserScript::RunLocation run_location) const override;
+ bool ShouldInjectCss(UserScript::RunLocation run_location) const override;
+ PermissionsData::AccessType CanExecuteOnFrame(
+ const InjectionHost* injection_host,
+ blink::WebLocalFrame* web_frame,
+ int tab_id) const override;
+ std::vector<blink::WebScriptSource> GetJsSources(
+ UserScript::RunLocation run_location) const override;
+ std::vector<std::string> GetCssSources(
+ UserScript::RunLocation run_location) const override;
+ void GetRunInfo(ScriptsRunInfo* scripts_run_info,
+ UserScript::RunLocation run_location) const override;
+ void OnInjectionComplete(scoped_ptr<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) override;
+ void OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) override;
+
+ // The associated user script. Owned by the UserScriptInjector that created
+ // this object.
+ const UserScript* script_;
+
+ // The id of the associated user script. We cache this because when we update
+ // the |script_| associated with this injection, the old referance may be
+ // deleted.
+ int script_id_;
+
+ // The associated host id, preserved for the same reason as |script_id|.
+ HostID host_id_;
+
+ // Indicates whether or not this script is declarative. This influences which
+ // script permissions are checked before injection.
+ bool is_declarative_;
+
+ ScopedObserver<UserScriptSet, UserScriptSet::Observer>
+ user_script_set_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserScriptInjector);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_USER_SCRIPT_INJECTOR_H_
diff --git a/chromium/extensions/renderer/user_script_set.cc b/chromium/extensions/renderer/user_script_set.cc
new file mode 100644
index 00000000000..e289f3fc56a
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_set.cc
@@ -0,0 +1,235 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/user_script_set.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/memory/ref_counted.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/renderer/extension_injection_host.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+#include "extensions/renderer/injection_host.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/user_script_injector.h"
+#include "extensions/renderer/web_ui_injection_host.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+GURL GetDocumentUrlForFrame(blink::WebLocalFrame* frame) {
+ GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame);
+ if (!data_source_url.is_empty() && frame->isViewSourceModeEnabled()) {
+ data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
+ data_source_url.spec());
+ }
+
+ return data_source_url;
+}
+
+} // namespace
+
+UserScriptSet::UserScriptSet() {}
+
+UserScriptSet::~UserScriptSet() {
+}
+
+void UserScriptSet::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UserScriptSet::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void UserScriptSet::GetActiveExtensionIds(
+ std::set<std::string>* ids) const {
+ for (const UserScript* script : scripts_) {
+ if (script->host_id().type() != HostID::EXTENSIONS)
+ continue;
+ DCHECK(!script->extension_id().empty());
+ ids->insert(script->extension_id());
+ }
+}
+
+void UserScriptSet::GetInjections(
+ std::vector<scoped_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location) {
+ GURL document_url = GetDocumentUrlForFrame(render_frame->GetWebFrame());
+ for (const UserScript* script : scripts_) {
+ scoped_ptr<ScriptInjection> injection = GetInjectionForScript(
+ script,
+ render_frame,
+ tab_id,
+ run_location,
+ document_url,
+ false /* is_declarative */);
+ if (injection.get())
+ injections->push_back(std::move(injection));
+ }
+}
+
+bool UserScriptSet::UpdateUserScripts(base::SharedMemoryHandle shared_memory,
+ const std::set<HostID>& changed_hosts,
+ bool whitelisted_only) {
+ bool only_inject_incognito =
+ ExtensionsRendererClient::Get()->IsIncognitoProcess();
+
+ // Create the shared memory object (read only).
+ shared_memory_.reset(new base::SharedMemory(shared_memory, true));
+ if (!shared_memory_.get())
+ return false;
+
+ // First get the size of the memory block.
+ if (!shared_memory_->Map(sizeof(base::Pickle::Header)))
+ return false;
+ base::Pickle::Header* pickle_header =
+ reinterpret_cast<base::Pickle::Header*>(shared_memory_->memory());
+
+ // Now map in the rest of the block.
+ int pickle_size = sizeof(base::Pickle::Header) + pickle_header->payload_size;
+ shared_memory_->Unmap();
+ if (!shared_memory_->Map(pickle_size))
+ return false;
+
+ // Unpickle scripts.
+ uint32_t num_scripts = 0;
+ base::Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()),
+ pickle_size);
+ base::PickleIterator iter(pickle);
+ CHECK(iter.ReadUInt32(&num_scripts));
+
+ scripts_.clear();
+ scripts_.reserve(num_scripts);
+ for (uint32_t i = 0; i < num_scripts; ++i) {
+ scoped_ptr<UserScript> script(new UserScript());
+ script->Unpickle(pickle, &iter);
+
+ // Note that this is a pointer into shared memory. We don't own it. It gets
+ // cleared up when the last renderer or browser process drops their
+ // reference to the shared memory.
+ for (size_t j = 0; j < script->js_scripts().size(); ++j) {
+ const char* body = NULL;
+ int body_length = 0;
+ CHECK(iter.ReadData(&body, &body_length));
+ script->js_scripts()[j].set_external_content(
+ base::StringPiece(body, body_length));
+ }
+ for (size_t j = 0; j < script->css_scripts().size(); ++j) {
+ const char* body = NULL;
+ int body_length = 0;
+ CHECK(iter.ReadData(&body, &body_length));
+ script->css_scripts()[j].set_external_content(
+ base::StringPiece(body, body_length));
+ }
+
+ if (only_inject_incognito && !script->is_incognito_enabled())
+ continue; // This script shouldn't run in an incognito tab.
+
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(script->extension_id());
+ if (whitelisted_only &&
+ (!extension ||
+ !PermissionsData::CanExecuteScriptEverywhere(extension))) {
+ continue;
+ }
+
+ scripts_.push_back(std::move(script));
+ }
+
+ FOR_EACH_OBSERVER(Observer,
+ observers_,
+ OnUserScriptsUpdated(changed_hosts, scripts_.get()));
+ return true;
+}
+
+scoped_ptr<ScriptInjection> UserScriptSet::GetDeclarativeScriptInjection(
+ int script_id,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ const GURL& document_url) {
+ for (const UserScript* script : scripts_) {
+ if (script->id() == script_id) {
+ return GetInjectionForScript(script,
+ render_frame,
+ tab_id,
+ run_location,
+ document_url,
+ true /* is_declarative */);
+ }
+ }
+ return scoped_ptr<ScriptInjection>();
+}
+
+scoped_ptr<ScriptInjection> UserScriptSet::GetInjectionForScript(
+ const UserScript* script,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ const GURL& document_url,
+ bool is_declarative) {
+ scoped_ptr<ScriptInjection> injection;
+ scoped_ptr<const InjectionHost> injection_host;
+ blink::WebLocalFrame* web_frame = render_frame->GetWebFrame();
+
+ const HostID& host_id = script->host_id();
+ if (host_id.type() == HostID::EXTENSIONS) {
+ injection_host = ExtensionInjectionHost::Create(host_id.id());
+ if (!injection_host)
+ return injection;
+ } else {
+ DCHECK_EQ(host_id.type(), HostID::WEBUI);
+ injection_host.reset(new WebUIInjectionHost(host_id));
+ }
+
+ if (web_frame->parent() && !script->match_all_frames())
+ return injection; // Only match subframes if the script declared it.
+
+ GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
+ web_frame, document_url, script->match_about_blank());
+
+ if (!script->MatchesURL(effective_document_url))
+ return injection;
+
+ scoped_ptr<ScriptInjector> injector(new UserScriptInjector(script,
+ this,
+ is_declarative));
+
+ if (injector->CanExecuteOnFrame(
+ injection_host.get(),
+ web_frame,
+ tab_id) ==
+ PermissionsData::ACCESS_DENIED) {
+ return injection;
+ }
+
+ bool inject_css = !script->css_scripts().empty() &&
+ run_location == UserScript::DOCUMENT_START;
+ bool inject_js =
+ !script->js_scripts().empty() && script->run_location() == run_location;
+ if (inject_css || inject_js) {
+ injection.reset(new ScriptInjection(std::move(injector), render_frame,
+ std::move(injection_host),
+ run_location));
+ }
+ return injection;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/user_script_set.h b/chromium/extensions/renderer/user_script_set.h
new file mode 100644
index 00000000000..128b33f0be9
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_set.h
@@ -0,0 +1,100 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_USER_SCRIPT_SET_H_
+#define EXTENSIONS_RENDERER_USER_SCRIPT_SET_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/shared_memory.h"
+#include "base/observer_list.h"
+#include "content/public/renderer/render_process_observer.h"
+#include "extensions/common/user_script.h"
+
+class GURL;
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+class ScriptInjection;
+
+// The UserScriptSet is a collection of UserScripts which knows how to update
+// itself from SharedMemory and create ScriptInjections for UserScripts to
+// inject on a page.
+class UserScriptSet {
+ public:
+ class Observer {
+ public:
+ virtual void OnUserScriptsUpdated(
+ const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) = 0;
+ };
+
+ UserScriptSet();
+ ~UserScriptSet();
+
+ // Adds or removes observers.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Appends the ids of the extensions that have user scripts to |ids|.
+ void GetActiveExtensionIds(std::set<std::string>* ids) const;
+
+ // Append any ScriptInjections that should run on the given |render_frame| and
+ // |tab_id|, at the given |run_location|, to |injections|.
+ // |extensions| is passed in to verify the corresponding extension is still
+ // valid.
+ void GetInjections(std::vector<scoped_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location);
+
+ scoped_ptr<ScriptInjection> GetDeclarativeScriptInjection(
+ int script_id,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ const GURL& document_url);
+
+ // Updates scripts given the shared memory region containing user scripts.
+ // Returns true if the scripts were successfully updated.
+ bool UpdateUserScripts(base::SharedMemoryHandle shared_memory,
+ const std::set<HostID>& changed_hosts,
+ bool whitelisted_only);
+
+ const std::vector<UserScript*>& scripts() const { return scripts_.get(); }
+
+ private:
+ // Returns a new ScriptInjection for the given |script| to execute in the
+ // |render_frame|, or NULL if the script should not execute.
+ scoped_ptr<ScriptInjection> GetInjectionForScript(
+ const UserScript* script,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ const GURL& document_url,
+ bool is_declarative);
+
+ // Shared memory containing raw script data.
+ scoped_ptr<base::SharedMemory> shared_memory_;
+
+ // The UserScripts this injector manages.
+ ScopedVector<UserScript> scripts_;
+
+ // The associated observers.
+ base::ObserverList<Observer> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserScriptSet);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_USER_SCRIPT_SET_H_
diff --git a/chromium/extensions/renderer/user_script_set_manager.cc b/chromium/extensions/renderer/user_script_set_manager.cc
new file mode 100644
index 00000000000..0404e0552b9
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_set_manager.cc
@@ -0,0 +1,159 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/user_script_set_manager.h"
+
+#include "components/crx_file/id_util.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/user_script_set.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace extensions {
+
+UserScriptSetManager::UserScriptSetManager() {
+ content::RenderThread::Get()->AddObserver(this);
+}
+
+UserScriptSetManager::~UserScriptSetManager() {
+}
+
+void UserScriptSetManager::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UserScriptSetManager::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+scoped_ptr<ScriptInjection>
+UserScriptSetManager::GetInjectionForDeclarativeScript(
+ int script_id,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ const GURL& url,
+ const std::string& extension_id) {
+ UserScriptSet* user_script_set =
+ GetProgrammaticScriptsByHostID(HostID(HostID::EXTENSIONS, extension_id));
+ if (!user_script_set)
+ return scoped_ptr<ScriptInjection>();
+
+ return user_script_set->GetDeclarativeScriptInjection(
+ script_id,
+ render_frame,
+ tab_id,
+ UserScript::BROWSER_DRIVEN,
+ url);
+}
+
+bool UserScriptSetManager::OnControlMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(UserScriptSetManager, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void UserScriptSetManager::GetAllInjections(
+ std::vector<scoped_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location) {
+ static_scripts_.GetInjections(injections, render_frame, tab_id, run_location);
+ for (UserScriptSetMap::iterator it = programmatic_scripts_.begin();
+ it != programmatic_scripts_.end();
+ ++it) {
+ it->second->GetInjections(injections, render_frame, tab_id, run_location);
+ }
+}
+
+void UserScriptSetManager::GetAllActiveExtensionIds(
+ std::set<std::string>* ids) const {
+ DCHECK(ids);
+ static_scripts_.GetActiveExtensionIds(ids);
+ for (UserScriptSetMap::const_iterator it = programmatic_scripts_.begin();
+ it != programmatic_scripts_.end();
+ ++it) {
+ it->second->GetActiveExtensionIds(ids);
+ }
+}
+
+UserScriptSet* UserScriptSetManager::GetProgrammaticScriptsByHostID(
+ const HostID& host_id) {
+ UserScriptSetMap::const_iterator it = programmatic_scripts_.find(host_id);
+ return it != programmatic_scripts_.end() ? it->second.get() : NULL;
+}
+
+void UserScriptSetManager::OnUpdateUserScripts(
+ base::SharedMemoryHandle shared_memory,
+ const HostID& host_id,
+ const std::set<HostID>& changed_hosts,
+ bool whitelisted_only) {
+ if (!base::SharedMemory::IsHandleValid(shared_memory)) {
+ NOTREACHED() << "Bad scripts handle";
+ return;
+ }
+
+ for (const HostID& host_id : changed_hosts) {
+ if (host_id.type() == HostID::EXTENSIONS &&
+ !crx_file::id_util::IdIsValid(host_id.id())) {
+ NOTREACHED() << "Invalid extension id: " << host_id.id();
+ return;
+ }
+ }
+
+ UserScriptSet* scripts = NULL;
+ if (!host_id.id().empty()) {
+ // The expectation when there is a host that "owns" this shared
+ // memory region is that the |changed_hosts| is either the empty list
+ // or just the owner.
+ CHECK(changed_hosts.size() <= 1);
+ if (programmatic_scripts_.find(host_id) == programmatic_scripts_.end()) {
+ scripts = new UserScriptSet();
+ programmatic_scripts_[host_id] = make_linked_ptr(scripts);
+ } else {
+ scripts = programmatic_scripts_[host_id].get();
+ }
+ } else {
+ scripts = &static_scripts_;
+ }
+ DCHECK(scripts);
+
+ // If no hosts are included in the set, that indicates that all
+ // hosts were updated. Add them all to the set so that observers and
+ // individual UserScriptSets don't need to know this detail.
+ const std::set<HostID>* effective_hosts = &changed_hosts;
+ std::set<HostID> all_hosts;
+ if (changed_hosts.empty()) {
+ // The meaning of "all hosts(extensions)" varies, depending on whether some
+ // host "owns" this shared memory region.
+ // No owner => all known hosts.
+ // Owner => just the owner host.
+ if (host_id.id().empty()) {
+ std::set<std::string> extension_ids =
+ RendererExtensionRegistry::Get()->GetIDs();
+ for (const std::string& extension_id : extension_ids)
+ all_hosts.insert(HostID(HostID::EXTENSIONS, extension_id));
+ } else {
+ all_hosts.insert(host_id);
+ }
+ effective_hosts = &all_hosts;
+ }
+
+ if (scripts->UpdateUserScripts(shared_memory,
+ *effective_hosts,
+ whitelisted_only)) {
+ FOR_EACH_OBSERVER(
+ Observer,
+ observers_,
+ OnUserScriptsUpdated(*effective_hosts, scripts->scripts()));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/user_script_set_manager.h b/chromium/extensions/renderer/user_script_set_manager.h
new file mode 100644
index 00000000000..5cba81e7bc2
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_set_manager.h
@@ -0,0 +1,113 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_USER_SCRIPT_SET_MANAGER_H_
+#define EXTENSIONS_RENDERER_USER_SCRIPT_SET_MANAGER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/observer_list.h"
+#include "content/public/renderer/render_process_observer.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/user_script.h"
+#include "extensions/renderer/user_script_set.h"
+
+namespace content {
+class RenderFrame;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace extensions {
+
+class ScriptInjection;
+
+// Manager for separate UserScriptSets, one for each shared memory region.
+// Regions are organized as follows:
+// static_scripts -- contains all extensions' scripts that are statically
+// declared in the extension manifest.
+// programmatic_scripts -- one region per host (extension or WebUI) containing
+// only programmatically-declared scripts, instantiated
+// when an extension first creates a declarative rule
+// that would, if triggered, request a script injection.
+class UserScriptSetManager : public content::RenderProcessObserver {
+ public:
+ // Like a UserScriptSet::Observer, but automatically subscribes to all sets
+ // associated with the manager.
+ class Observer {
+ public:
+ virtual void OnUserScriptsUpdated(
+ const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) = 0;
+ };
+
+ UserScriptSetManager();
+
+ ~UserScriptSetManager() override;
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Looks up the script injection associated with |script_id| and
+ // |extension_id| in the context of the given |web_frame|, |tab_id|,
+ // and |url|.
+ scoped_ptr<ScriptInjection> GetInjectionForDeclarativeScript(
+ int script_id,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ const GURL& url,
+ const std::string& extension_id);
+
+ // Append all injections from |static_scripts| and each of
+ // |programmatic_scripts_| to |injections|.
+ void GetAllInjections(std::vector<scoped_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location);
+
+ // Get active extension IDs from |static_scripts| and each of
+ // |programmatic_scripts_|.
+ void GetAllActiveExtensionIds(std::set<std::string>* ids) const;
+
+ const UserScriptSet* static_scripts() const { return &static_scripts_; }
+
+ private:
+ // Map for per-extension sets that may be defined programmatically.
+ typedef std::map<HostID, linked_ptr<UserScriptSet> > UserScriptSetMap;
+
+ // content::RenderProcessObserver implementation.
+ bool OnControlMessageReceived(const IPC::Message& message) override;
+
+ UserScriptSet* GetProgrammaticScriptsByHostID(const HostID& host_id);
+
+ // Handle the UpdateUserScripts extension message.
+ void OnUpdateUserScripts(base::SharedMemoryHandle shared_memory,
+ const HostID& host_id,
+ const std::set<HostID>& changed_hosts,
+ bool whitelisted_only);
+
+ // Scripts statically defined in extension manifests.
+ UserScriptSet static_scripts_;
+
+ // Scripts programmatically-defined through API calls (initialized and stored
+ // per-extension).
+ UserScriptSetMap programmatic_scripts_;
+
+ // The associated observers.
+ base::ObserverList<Observer> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserScriptSetManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_USER_SCRIPT_SET_MANAGER_H_
diff --git a/chromium/extensions/renderer/utils_native_handler.cc b/chromium/extensions/renderer/utils_native_handler.cc
new file mode 100644
index 00000000000..741a111b779
--- /dev/null
+++ b/chromium/extensions/renderer/utils_native_handler.cc
@@ -0,0 +1,29 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/utils_native_handler.h"
+
+#include "base/macros.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebSerializedScriptValue.h"
+
+namespace extensions {
+
+UtilsNativeHandler::UtilsNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "deepCopy",
+ base::Bind(&UtilsNativeHandler::DeepCopy, base::Unretained(this)));
+}
+
+UtilsNativeHandler::~UtilsNativeHandler() {}
+
+void UtilsNativeHandler::DeepCopy(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ args.GetReturnValue().Set(
+ blink::WebSerializedScriptValue::serialize(args[0]).deserialize());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/utils_native_handler.h b/chromium/extensions/renderer/utils_native_handler.h
new file mode 100644
index 00000000000..6a2ae4da350
--- /dev/null
+++ b/chromium/extensions/renderer/utils_native_handler.h
@@ -0,0 +1,30 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_UTILS_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_UTILS_NATIVE_HANDLER_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+class UtilsNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit UtilsNativeHandler(ScriptContext* context);
+ ~UtilsNativeHandler() override;
+
+ private:
+ // |args| consists of one argument: an arbitrary value. Returns a deep copy of
+ // that value. The copy will have no references to nested values of the
+ // argument.
+ void DeepCopy(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(UtilsNativeHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_UTILS_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/utils_unittest.cc b/chromium/extensions/renderer/utils_unittest.cc
new file mode 100644
index 00000000000..485c214ea66
--- /dev/null
+++ b/chromium/extensions/renderer/utils_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 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.
+
+#include "base/strings/stringprintf.h"
+#include "extensions/renderer/module_system_test.h"
+#include "gin/dictionary.h"
+#include "grit/extensions_renderer_resources.h"
+
+namespace extensions {
+namespace {
+
+class UtilsUnittest : public ModuleSystemTest {
+ private:
+ void SetUp() override {
+ ModuleSystemTest::SetUp();
+
+ env()->RegisterModule("utils", IDR_UTILS_JS);
+ env()->RegisterTestFile("utils_unittest", "utils_unittest.js");
+ env()->OverrideNativeHandler("schema_registry",
+ "exports.$set('GetSchema', function() {});");
+ env()->OverrideNativeHandler("logging",
+ "exports.$set('CHECK', function() {});\n"
+ "exports.$set('DCHECK', function() {});\n"
+ "exports.$set('WARNING', function() {});");
+ env()->OverrideNativeHandler("v8_context", "");
+ gin::Dictionary chrome(env()->isolate(), env()->CreateGlobal("chrome"));
+ gin::Dictionary chrome_runtime(
+ gin::Dictionary::CreateEmpty(env()->isolate()));
+ chrome.Set("runtime", chrome_runtime);
+ }
+};
+
+TEST_F(UtilsUnittest, TestNothing) {
+ ExpectNoAssertionsMade();
+}
+
+TEST_F(UtilsUnittest, SuperClass) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->CallModuleMethod("utils_unittest", "testSuperClass");
+}
+
+TEST_F(UtilsUnittest, PromiseNoResult) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->CallModuleMethod("utils_unittest",
+ "testPromiseNoResult");
+ RunResolvedPromises();
+}
+
+TEST_F(UtilsUnittest, PromiseOneResult) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->CallModuleMethod("utils_unittest",
+ "testPromiseOneResult");
+ RunResolvedPromises();
+}
+
+TEST_F(UtilsUnittest, PromiseTwoResults) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->CallModuleMethod("utils_unittest",
+ "testPromiseTwoResults");
+ RunResolvedPromises();
+}
+
+TEST_F(UtilsUnittest, PromiseError) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->CallModuleMethod("utils_unittest",
+ "testPromiseError");
+ RunResolvedPromises();
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/v8_context_native_handler.cc b/chromium/extensions/renderer/v8_context_native_handler.cc
new file mode 100644
index 00000000000..1ac53c5be00
--- /dev/null
+++ b/chromium/extensions/renderer/v8_context_native_handler.cc
@@ -0,0 +1,64 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/v8_context_native_handler.h"
+
+#include "base/bind.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+namespace extensions {
+
+V8ContextNativeHandler::V8ContextNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context), context_(context) {
+ RouteFunction("GetAvailability",
+ base::Bind(&V8ContextNativeHandler::GetAvailability,
+ base::Unretained(this)));
+ RouteFunction("GetModuleSystem",
+ base::Bind(&V8ContextNativeHandler::GetModuleSystem,
+ base::Unretained(this)));
+ RouteFunction(
+ "RunWithNativesEnabled",
+ base::Bind(&V8ContextNativeHandler::RunWithNativesEnabled,
+ base::Unretained(this)));
+}
+
+void V8ContextNativeHandler::GetAvailability(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ v8::Isolate* isolate = args.GetIsolate();
+ std::string api_name = *v8::String::Utf8Value(args[0]);
+ Feature::Availability availability = context_->GetAvailability(api_name);
+
+ v8::Local<v8::Object> ret = v8::Object::New(isolate);
+ ret->Set(v8::String::NewFromUtf8(isolate, "is_available"),
+ v8::Boolean::New(isolate, availability.is_available()));
+ ret->Set(v8::String::NewFromUtf8(isolate, "message"),
+ v8::String::NewFromUtf8(isolate, availability.message().c_str()));
+ ret->Set(v8::String::NewFromUtf8(isolate, "result"),
+ v8::Integer::New(isolate, availability.result()));
+ args.GetReturnValue().Set(ret);
+}
+
+void V8ContextNativeHandler::GetModuleSystem(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsObject());
+ ScriptContext* context = ScriptContextSet::GetContextByObject(
+ v8::Local<v8::Object>::Cast(args[0]));
+ if (blink::WebFrame::scriptCanAccess(context->web_frame()))
+ args.GetReturnValue().Set(context->module_system()->NewInstance());
+}
+
+void V8ContextNativeHandler::RunWithNativesEnabled(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsFunction());
+ ModuleSystem::NativesEnabledScope natives_enabled(context()->module_system());
+ context()->CallFunction(v8::Local<v8::Function>::Cast(args[0]));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/v8_context_native_handler.h b/chromium/extensions/renderer/v8_context_native_handler.h
new file mode 100644
index 00000000000..956ef6c68ce
--- /dev/null
+++ b/chromium/extensions/renderer/v8_context_native_handler.h
@@ -0,0 +1,29 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_V8_CONTEXT_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_V8_CONTEXT_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class Dispatcher;
+
+class V8ContextNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit V8ContextNativeHandler(ScriptContext* context);
+
+ private:
+ void GetAvailability(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetModuleSystem(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ void RunWithNativesEnabled(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ ScriptContext* context_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_V8_CONTEXT_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/v8_helpers.h b/chromium/extensions/renderer/v8_helpers.h
new file mode 100644
index 00000000000..0a3b2ebaad9
--- /dev/null
+++ b/chromium/extensions/renderer/v8_helpers.h
@@ -0,0 +1,166 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_V8_HELPERS_H_
+#define EXTENSIONS_RENDERER_V8_HELPERS_H_
+
+#include <stdint.h>
+#include <string.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+namespace v8_helpers {
+
+// Helper functions for V8 APIs.
+
+// Converts |str| to a V8 string. Returns true on success.
+inline bool ToV8String(v8::Isolate* isolate,
+ const char* str,
+ v8::Local<v8::String>* out) {
+ return v8::String::NewFromUtf8(isolate, str, v8::NewStringType::kNormal)
+ .ToLocal(out);
+}
+
+inline bool ToV8String(v8::Isolate* isolate,
+ const std::string& str,
+ v8::Local<v8::String>* out) {
+ return ToV8String(isolate, str.c_str(), out);
+}
+
+// Converts |str| to a V8 string.
+// This crashes when strlen(str) > v8::String::kMaxLength.
+inline v8::Local<v8::String> ToV8StringUnsafe(
+ v8::Isolate* isolate,
+ const char* str,
+ v8::NewStringType string_type = v8::NewStringType::kNormal) {
+ DCHECK(strlen(str) <= v8::String::kMaxLength);
+ return v8::String::NewFromUtf8(isolate, str, string_type)
+ .ToLocalChecked();
+}
+
+inline v8::Local<v8::String> ToV8StringUnsafe(
+ v8::Isolate* isolate,
+ const std::string& str,
+ v8::NewStringType string_type = v8::NewStringType::kNormal) {
+ return ToV8StringUnsafe(isolate, str.c_str(), string_type);
+}
+
+// Returns true if |maybe| is both a value, and that value is true.
+inline bool IsTrue(v8::Maybe<bool> maybe) {
+ return maybe.IsJust() && maybe.FromJust();
+}
+
+// Returns true if |value| is empty or undefined.
+inline bool IsEmptyOrUndefied(v8::Local<v8::Value> value) {
+ return value.IsEmpty() || value->IsUndefined();
+}
+
+// SetProperty() family wraps V8::Object::DefineOwnProperty().
+// Returns true on success.
+// NOTE: Think about whether you want this or SetPrivateProperty() below.
+// TODO(devlin): Sort through more of the callers of this and see if we can
+// convert more to be private.
+inline bool SetProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ v8::Local<v8::String> key,
+ v8::Local<v8::Value> value) {
+ return IsTrue(object->DefineOwnProperty(context, key, value));
+}
+
+// Wraps v8::Object::SetPrivate(). When possible, prefer this to SetProperty().
+inline bool SetPrivateProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ v8::Local<v8::String> key,
+ v8::Local<v8::Value> value) {
+ return IsTrue(object->SetPrivate(
+ context, v8::Private::ForApi(context->GetIsolate(), key), value));
+}
+
+inline bool SetPrivateProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ const char* key,
+ v8::Local<v8::Value> value) {
+ v8::Local<v8::String> v8_key;
+ return ToV8String(context->GetIsolate(), key, &v8_key) &&
+ IsTrue(object->SetPrivate(
+ context, v8::Private::ForApi(context->GetIsolate(), v8_key),
+ value));
+}
+
+// GetProperty() family calls V8::Object::Get() and extracts a value from
+// returned MaybeLocal. Returns true on success.
+// NOTE: Think about whether you want this or GetPrivateProperty() below.
+template <typename Key>
+inline bool GetProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ Key key,
+ v8::Local<v8::Value>* out) {
+ return object->Get(context, key).ToLocal(out);
+}
+
+inline bool GetProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ const char* key,
+ v8::Local<v8::Value>* out) {
+ v8::Local<v8::String> v8_key;
+ if (!ToV8String(context->GetIsolate(), key, &v8_key))
+ return false;
+ return GetProperty(context, object, v8_key, out);
+}
+
+// Wraps v8::Object::GetPrivate(). When possible, prefer this to GetProperty().
+inline bool GetPrivateProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ v8::Local<v8::String> key,
+ v8::Local<v8::Value>* out) {
+ return object
+ ->GetPrivate(context, v8::Private::ForApi(context->GetIsolate(), key))
+ .ToLocal(out);
+}
+
+inline bool GetPrivateProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ const char* key,
+ v8::Local<v8::Value>* out) {
+ v8::Local<v8::String> v8_key;
+ return ToV8String(context->GetIsolate(), key, &v8_key) &&
+ GetPrivateProperty(context, object, v8_key, out);
+}
+
+// GetPropertyUnsafe() family wraps v8::Object::Get(). They crash when an
+// exception is thrown.
+inline v8::Local<v8::Value> GetPropertyUnsafe(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ v8::Local<v8::Value> key) {
+ return object->Get(context, key).ToLocalChecked();
+}
+
+inline v8::Local<v8::Value> GetPropertyUnsafe(
+ v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ const char* key,
+ v8::NewStringType string_type = v8::NewStringType::kNormal) {
+ return object->Get(context,
+ ToV8StringUnsafe(context->GetIsolate(), key, string_type))
+ .ToLocalChecked();
+}
+
+// Wraps v8::Function::Call(). Returns true on success.
+inline bool CallFunction(v8::Local<v8::Context> context,
+ v8::Local<v8::Function> function,
+ v8::Local<v8::Value> recv,
+ int argc,
+ v8::Local<v8::Value> argv[],
+ v8::Local<v8::Value>* out) {
+ v8::MicrotasksScope microtasks_scope(
+ context->GetIsolate(), v8::MicrotasksScope::kDoNotRunMicrotasks);
+ return function->Call(context, recv, argc, argv).ToLocal(out);
+}
+
+} // namespace v8_helpers
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_V8_HELPERS_H_
diff --git a/chromium/extensions/renderer/v8_schema_registry.cc b/chromium/extensions/renderer/v8_schema_registry.cc
new file mode 100644
index 00000000000..3bd7c479122
--- /dev/null
+++ b/chromium/extensions/renderer/v8_schema_registry.cc
@@ -0,0 +1,142 @@
+// Copyright 2014 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.
+
+#include "extensions/renderer/v8_schema_registry.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "content/public/child/v8_value_converter.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "extensions/renderer/script_context.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+namespace {
+
+// Recursively freezes every v8 object on |object|.
+void DeepFreeze(const v8::Local<v8::Object>& object,
+ const v8::Local<v8::Context>& context) {
+ // Don't let the object trace upwards via the prototype.
+ v8::Maybe<bool> maybe =
+ object->SetPrototype(context, v8::Null(context->GetIsolate()));
+ CHECK(maybe.IsJust() && maybe.FromJust());
+ v8::Local<v8::Array> property_names = object->GetOwnPropertyNames();
+ for (uint32_t i = 0; i < property_names->Length(); ++i) {
+ v8::Local<v8::Value> child = object->Get(property_names->Get(i));
+ if (child->IsObject())
+ DeepFreeze(v8::Local<v8::Object>::Cast(child), context);
+ }
+ object->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen);
+}
+
+class SchemaRegistryNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ SchemaRegistryNativeHandler(V8SchemaRegistry* registry,
+ scoped_ptr<ScriptContext> context)
+ : ObjectBackedNativeHandler(context.get()),
+ context_(std::move(context)),
+ registry_(registry) {
+ RouteFunction("GetSchema",
+ base::Bind(&SchemaRegistryNativeHandler::GetSchema,
+ base::Unretained(this)));
+ }
+
+ ~SchemaRegistryNativeHandler() override { context_->Invalidate(); }
+
+ private:
+ void GetSchema(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(
+ registry_->GetSchema(*v8::String::Utf8Value(args[0])));
+ }
+
+ scoped_ptr<ScriptContext> context_;
+ V8SchemaRegistry* registry_;
+};
+
+} // namespace
+
+V8SchemaRegistry::V8SchemaRegistry() {
+}
+
+V8SchemaRegistry::~V8SchemaRegistry() {
+}
+
+scoped_ptr<NativeHandler> V8SchemaRegistry::AsNativeHandler() {
+ scoped_ptr<ScriptContext> context(
+ new ScriptContext(GetOrCreateContext(v8::Isolate::GetCurrent()),
+ NULL, // no frame
+ NULL, // no extension
+ Feature::UNSPECIFIED_CONTEXT,
+ NULL, // no effective extension
+ Feature::UNSPECIFIED_CONTEXT));
+ return scoped_ptr<NativeHandler>(
+ new SchemaRegistryNativeHandler(this, std::move(context)));
+}
+
+v8::Local<v8::Array> V8SchemaRegistry::GetSchemas(
+ const std::vector<std::string>& apis) {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(GetOrCreateContext(isolate));
+
+ v8::Local<v8::Array> v8_apis(v8::Array::New(isolate, apis.size()));
+ size_t api_index = 0;
+ for (std::vector<std::string>::const_iterator i = apis.begin();
+ i != apis.end();
+ ++i) {
+ v8_apis->Set(api_index++, GetSchema(*i));
+ }
+ return handle_scope.Escape(v8_apis);
+}
+
+v8::Local<v8::Object> V8SchemaRegistry::GetSchema(const std::string& api) {
+ if (schema_cache_ != NULL) {
+ v8::Local<v8::Object> cached_schema = schema_cache_->Get(api);
+ if (!cached_schema.IsEmpty()) {
+ return cached_schema;
+ }
+ }
+
+ // Slow path: Need to build schema first.
+
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context = GetOrCreateContext(isolate);
+ v8::Context::Scope context_scope(context);
+
+ const base::DictionaryValue* schema =
+ ExtensionAPI::GetSharedInstance()->GetSchema(api);
+ CHECK(schema) << api;
+ scoped_ptr<V8ValueConverter> v8_value_converter(V8ValueConverter::create());
+ v8::Local<v8::Value> value = v8_value_converter->ToV8Value(schema, context);
+ CHECK(!value.IsEmpty());
+
+ v8::Local<v8::Object> v8_schema(v8::Local<v8::Object>::Cast(value));
+ DeepFreeze(v8_schema, context);
+ schema_cache_->Set(api, v8_schema);
+
+ return handle_scope.Escape(v8_schema);
+}
+
+v8::Local<v8::Context> V8SchemaRegistry::GetOrCreateContext(
+ v8::Isolate* isolate) {
+ // It's ok to create local handles in this function, since this is only called
+ // when we have a HandleScope.
+ if (!context_holder_) {
+ context_holder_.reset(new gin::ContextHolder(isolate));
+ context_holder_->SetContext(v8::Context::New(isolate));
+ schema_cache_.reset(new SchemaCache(isolate));
+ return context_holder_->context();
+ }
+ return context_holder_->context();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/v8_schema_registry.h b/chromium/extensions/renderer/v8_schema_registry.h
new file mode 100644
index 00000000000..08f54b62658
--- /dev/null
+++ b/chromium/extensions/renderer/v8_schema_registry.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_RENDERER_V8_SCHEMA_REGISTRY_H_
+#define EXTENSIONS_RENDERER_V8_SCHEMA_REGISTRY_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "gin/public/context_holder.h"
+#include "v8/include/v8-util.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class NativeHandler;
+
+// A registry for the v8::Value representations of extension API schemas.
+// In a way, the v8 counterpart to ExtensionAPI.
+class V8SchemaRegistry {
+ public:
+ V8SchemaRegistry();
+ ~V8SchemaRegistry();
+
+ // Creates a NativeHandler wrapper |this|. Supports GetSchema.
+ scoped_ptr<NativeHandler> AsNativeHandler();
+
+ // Returns a v8::Array with all the schemas for the APIs in |apis|.
+ v8::Local<v8::Array> GetSchemas(const std::vector<std::string>& apis);
+
+ // Returns a v8::Object for the schema for |api|, possibly from the cache.
+ v8::Local<v8::Object> GetSchema(const std::string& api);
+
+ private:
+ // Gets the separate context that backs the registry, creating a new one if
+ // if necessary. Will also initialize schema_cache_.
+ v8::Local<v8::Context> GetOrCreateContext(v8::Isolate* isolate);
+
+ // Cache of schemas. Created lazily by GetOrCreateContext.
+ typedef v8::StdGlobalValueMap<std::string, v8::Object> SchemaCache;
+ scoped_ptr<SchemaCache> schema_cache_;
+
+ // Single per-instance gin::ContextHolder to create v8::Values.
+ // Created lazily via GetOrCreateContext.
+ scoped_ptr<gin::ContextHolder> context_holder_;
+
+ DISALLOW_COPY_AND_ASSIGN(V8SchemaRegistry);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_V8_SCHEMA_REGISTRY_H_
diff --git a/chromium/extensions/renderer/wake_event_page.cc b/chromium/extensions/renderer/wake_event_page.cc
new file mode 100644
index 00000000000..e3e21899bef
--- /dev/null
+++ b/chromium/extensions/renderer/wake_event_page.cc
@@ -0,0 +1,200 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/wake_event_page.h"
+
+#include <utility>
+
+#include "base/atomic_sequence_num.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/child/worker_thread.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+namespace {
+
+base::LazyInstance<WakeEventPage> g_instance = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+class WakeEventPage::WakeEventPageNativeHandler
+ : public ObjectBackedNativeHandler {
+ public:
+ // Handles own lifetime.
+ WakeEventPageNativeHandler(ScriptContext* context,
+ const std::string& name,
+ const MakeRequestCallback& make_request)
+ : ObjectBackedNativeHandler(context),
+ make_request_(make_request),
+ weak_ptr_factory_(this) {
+ // Use Unretained not a WeakPtr because RouteFunction is tied to the
+ // lifetime of this, so there is no way for DoWakeEventPage to be called
+ // after destruction.
+ RouteFunction(name, base::Bind(&WakeEventPageNativeHandler::DoWakeEventPage,
+ base::Unretained(this)));
+ // Delete self on invalidation. base::Unretained because by definition this
+ // can't be deleted before it's deleted.
+ context->AddInvalidationObserver(base::Bind(
+ &WakeEventPageNativeHandler::DeleteSelf, base::Unretained(this)));
+ };
+
+ ~WakeEventPageNativeHandler() override {}
+
+ private:
+ void DeleteSelf() {
+ Invalidate();
+ delete this;
+ }
+
+ // Called by JavaScript with a single argument, the function to call when the
+ // event page has been woken.
+ void DoWakeEventPage(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsFunction());
+ v8::Global<v8::Function> callback(args.GetIsolate(),
+ args[0].As<v8::Function>());
+
+ const std::string& extension_id = context()->GetExtensionID();
+ CHECK(!extension_id.empty());
+
+ make_request_.Run(
+ extension_id,
+ base::Bind(&WakeEventPageNativeHandler::OnEventPageIsAwake,
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
+ }
+
+ void OnEventPageIsAwake(v8::Global<v8::Function> callback, bool success) {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Value> args[] = {
+ v8::Boolean::New(isolate, success),
+ };
+ context()->CallFunction(v8::Local<v8::Function>::New(isolate, callback),
+ arraysize(args), args);
+ }
+
+ MakeRequestCallback make_request_;
+ base::WeakPtrFactory<WakeEventPageNativeHandler> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WakeEventPageNativeHandler);
+};
+
+// static
+WakeEventPage* WakeEventPage::Get() {
+ return g_instance.Pointer();
+}
+
+void WakeEventPage::Init(content::RenderThread* render_thread) {
+ DCHECK(render_thread);
+ DCHECK_EQ(content::RenderThread::Get(), render_thread);
+ DCHECK(!message_filter_);
+
+ message_filter_ = render_thread->GetSyncMessageFilter();
+ render_thread->AddObserver(this);
+}
+
+v8::Local<v8::Function> WakeEventPage::GetForContext(ScriptContext* context) {
+ DCHECK(message_filter_);
+
+ v8::Isolate* isolate = context->isolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Handle<v8::Context> v8_context = context->v8_context();
+ v8::Context::Scope context_scope(v8_context);
+
+ // Cache the imported function as a hidden property on the global object of
+ // |v8_context|. Creating it isn't free.
+ v8::Local<v8::Private> kWakeEventPageKey =
+ v8::Private::ForApi(isolate, ToV8StringUnsafe(isolate, "WakeEventPage"));
+ v8::Local<v8::Value> wake_event_page;
+ if (!v8_context->Global()
+ ->GetPrivate(v8_context, kWakeEventPageKey)
+ .ToLocal(&wake_event_page) ||
+ wake_event_page->IsUndefined()) {
+ // Implement this using a NativeHandler, which requires a function name
+ // (arbitrary in this case). Handles own lifetime.
+ const char* kFunctionName = "WakeEventPage";
+ WakeEventPageNativeHandler* native_handler = new WakeEventPageNativeHandler(
+ context, kFunctionName, base::Bind(&WakeEventPage::MakeRequest,
+ // Safe, owned by a LazyInstance.
+ base::Unretained(this)));
+
+ // Extract and cache the wake-event-page function from the native handler.
+ wake_event_page = GetPropertyUnsafe(
+ v8_context, native_handler->NewInstance(), kFunctionName);
+ v8_context->Global()
+ ->SetPrivate(v8_context, kWakeEventPageKey, wake_event_page)
+ .FromJust();
+ }
+
+ CHECK(wake_event_page->IsFunction());
+ return handle_scope.Escape(wake_event_page.As<v8::Function>());
+}
+
+WakeEventPage::RequestData::RequestData(int thread_id,
+ const OnResponseCallback& on_response)
+ : thread_id(thread_id), on_response(on_response) {}
+
+WakeEventPage::RequestData::~RequestData() {}
+
+WakeEventPage::WakeEventPage() {}
+
+WakeEventPage::~WakeEventPage() {}
+
+void WakeEventPage::MakeRequest(const std::string& extension_id,
+ const OnResponseCallback& on_response) {
+ static base::AtomicSequenceNumber sequence_number;
+ int request_id = sequence_number.GetNext();
+ {
+ scoped_ptr<RequestData> request_data(
+ new RequestData(content::WorkerThread::GetCurrentId(), on_response));
+ base::AutoLock lock(requests_lock_);
+ requests_.set(request_id, std::move(request_data));
+ }
+ message_filter_->Send(
+ new ExtensionHostMsg_WakeEventPage(request_id, extension_id));
+}
+
+bool WakeEventPage::OnControlMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(WakeEventPage, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_WakeEventPageResponse,
+ OnWakeEventPageResponse)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void WakeEventPage::OnWakeEventPageResponse(int request_id, bool success) {
+ scoped_ptr<RequestData> request_data;
+ {
+ base::AutoLock lock(requests_lock_);
+ request_data = requests_.take(request_id);
+ }
+ CHECK(request_data) << "No request with ID " << request_id;
+ if (request_data->thread_id == 0) {
+ // Thread ID of 0 means it wasn't called on a worker thread, so safe to
+ // call immediately.
+ request_data->on_response.Run(success);
+ } else {
+ content::WorkerThread::PostTask(
+ request_data->thread_id,
+ base::Bind(request_data->on_response, success));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/wake_event_page.h b/chromium/extensions/renderer/wake_event_page.h
new file mode 100644
index 00000000000..48bfd4bedb4
--- /dev/null
+++ b/chromium/extensions/renderer/wake_event_page.h
@@ -0,0 +1,116 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_WAKE_EVENT_PAGE_H_
+#define EXTENSIONS_RENDERER_WAKE_EVENT_PAGE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/containers/scoped_ptr_hash_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/lock.h"
+#include "content/public/renderer/render_process_observer.h"
+#include "ipc/ipc_sync_message_filter.h"
+#include "v8/include/v8.h"
+
+namespace content {
+class RenderThread;
+}
+
+namespace extensions {
+class ScriptContext;
+
+// This class implements the wake-event-page JavaScript function, which wakes
+// an event page and runs a callback when done.
+//
+// Note, the function will do a round trip to the browser even if event page is
+// open. Any optimisation to prevent this must be at the JavaScript level.
+class WakeEventPage : public content::RenderProcessObserver {
+ public:
+ WakeEventPage();
+ ~WakeEventPage() override;
+
+ // Returns the single instance of the WakeEventPage object.
+ //
+ // Thread safe.
+ static WakeEventPage* Get();
+
+ // Initializes the WakeEventPage.
+ //
+ // This must be called before any bindings are installed, and must be called
+ // on the render thread.
+ void Init(content::RenderThread* render_thread);
+
+ // Returns the wake-event-page function bound to a given context. The
+ // function will be cached as a hidden value in the context's global object.
+ //
+ // To mix C++ and JavaScript, example usage might be:
+ //
+ // WakeEventPage::Get().GetForContext(context)(function() {
+ // ...
+ // });
+ //
+ // Thread safe.
+ v8::Local<v8::Function> GetForContext(ScriptContext* context);
+
+ private:
+ class WakeEventPageNativeHandler;
+
+ // The response from an ExtensionHostMsg_WakeEvent call, passed true if the
+ // call was successful, false on failure.
+ using OnResponseCallback = base::Callback<void(bool)>;
+
+ // Makes an ExtensionHostMsg_WakeEvent request for an extension ID. The
+ // second argument is a callback to run when the request has completed.
+ using MakeRequestCallback =
+ base::Callback<void(const std::string&, const OnResponseCallback&)>;
+
+ // For |requests_|.
+ struct RequestData {
+ RequestData(int thread_id, const OnResponseCallback& on_response);
+ ~RequestData();
+
+ // The thread ID the request was made on. |on_response| must be called on
+ // that thread.
+ int thread_id;
+
+ // Callback to run when the response to the request arrives.
+ OnResponseCallback on_response;
+ };
+
+ // Runs |on_response|, passing it |success|.
+ static void RunOnResponseWithResult(const OnResponseCallback& on_response,
+ bool success);
+
+ // Sends the ExtensionHostMsg_WakeEvent IPC for |extension_id|, and
+ // updates |requests_| bookkeeping.
+ void MakeRequest(const std::string& extension_id,
+ const OnResponseCallback& on_response);
+
+ // content::RenderProcessObserver:
+ bool OnControlMessageReceived(const IPC::Message& message) override;
+
+ // OnControlMessageReceived handlers:
+ void OnWakeEventPageResponse(int request_id, bool success);
+
+ // IPC sender. Belongs to the render thread, but thread safe.
+ scoped_refptr<IPC::SyncMessageFilter> message_filter_;
+
+ // All in-flight requests, keyed by request ID. Used on multiple threads, so
+ // must be guarded by |requests_lock_|.
+ base::ScopedPtrHashMap<int, scoped_ptr<RequestData>> requests_;
+
+ // Lock for |requests_|.
+ base::Lock requests_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(WakeEventPage);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_WAKE_EVENT_PAGE_H_
diff --git a/chromium/extensions/renderer/web_ui_injection_host.cc b/chromium/extensions/renderer/web_ui_injection_host.cc
new file mode 100644
index 00000000000..1fd436452a8
--- /dev/null
+++ b/chromium/extensions/renderer/web_ui_injection_host.cc
@@ -0,0 +1,34 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/web_ui_injection_host.h"
+
+WebUIInjectionHost::WebUIInjectionHost(const HostID& host_id)
+ : InjectionHost(host_id),
+ url_(host_id.id()) {
+}
+
+WebUIInjectionHost::~WebUIInjectionHost() {
+}
+
+std::string WebUIInjectionHost::GetContentSecurityPolicy() const {
+ return std::string();
+}
+
+const GURL& WebUIInjectionHost::url() const {
+ return url_;
+}
+
+const std::string& WebUIInjectionHost::name() const {
+ return id().id();
+}
+
+extensions::PermissionsData::AccessType WebUIInjectionHost::CanExecuteOnFrame(
+ const GURL& document_url,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ bool is_declarative) const {
+ // Content scripts are allowed to inject on webviews created by WebUI.
+ return extensions::PermissionsData::AccessType::ACCESS_ALLOWED;
+}
diff --git a/chromium/extensions/renderer/web_ui_injection_host.h b/chromium/extensions/renderer/web_ui_injection_host.h
new file mode 100644
index 00000000000..a4df5f79fb1
--- /dev/null
+++ b/chromium/extensions/renderer/web_ui_injection_host.h
@@ -0,0 +1,33 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_WEBUI_INJECTION_HOST_H_
+#define EXTENSIONS_RENDERER_WEBUI_INJECTION_HOST_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/injection_host.h"
+
+class WebUIInjectionHost : public InjectionHost {
+ public:
+ WebUIInjectionHost(const HostID& host_id);
+ ~WebUIInjectionHost() override;
+
+ private:
+ // InjectionHost:
+ std::string GetContentSecurityPolicy() const override;
+ const GURL& url() const override;
+ const std::string& name() const override;
+ extensions::PermissionsData::AccessType CanExecuteOnFrame(
+ const GURL& document_url,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ bool is_declarative) const override;
+
+ private:
+ GURL url_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebUIInjectionHost);
+};
+
+#endif // EXTENSIONS_RENDERER_WEBUI_INJECTION_HOST_H_
diff --git a/chromium/extensions/renderer/worker_script_context_set.cc b/chromium/extensions/renderer/worker_script_context_set.cc
new file mode 100644
index 00000000000..a05fdbc77c6
--- /dev/null
+++ b/chromium/extensions/renderer/worker_script_context_set.cc
@@ -0,0 +1,82 @@
+// Copyright 2015 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.
+
+#include "extensions/renderer/worker_script_context_set.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+using ContextVector = ScopedVector<ScriptContext>;
+
+namespace {
+
+// Returns an iterator to the ScriptContext associated with |v8_context| from
+// |contexts|, or |contexts|->end() if not found.
+ContextVector::iterator FindContext(ContextVector* contexts,
+ v8::Local<v8::Context> v8_context) {
+ auto context_matches = [&v8_context](ScriptContext* context) {
+ v8::HandleScope handle_scope(context->isolate());
+ v8::Context::Scope context_scope(context->v8_context());
+ return context->v8_context() == v8_context;
+ };
+ return std::find_if(contexts->begin(), contexts->end(), context_matches);
+}
+
+} // namespace
+
+WorkerScriptContextSet::WorkerScriptContextSet() {}
+
+WorkerScriptContextSet::~WorkerScriptContextSet() {}
+
+void WorkerScriptContextSet::Insert(scoped_ptr<ScriptContext> context) {
+ DCHECK_GT(content::WorkerThread::GetCurrentId(), 0)
+ << "Must be called on a worker thread";
+ ContextVector* contexts = contexts_tls_.Get();
+ if (!contexts) {
+ // First context added for this thread. Create a new set, then wait for
+ // this thread's shutdown.
+ contexts = new ContextVector();
+ contexts_tls_.Set(contexts);
+ content::WorkerThread::AddObserver(this);
+ }
+ CHECK(FindContext(contexts, context->v8_context()) == contexts->end())
+ << "Worker for " << context->url() << " is already in this set";
+ contexts->push_back(std::move(context));
+}
+
+void WorkerScriptContextSet::Remove(v8::Local<v8::Context> v8_context,
+ const GURL& url) {
+ DCHECK_GT(content::WorkerThread::GetCurrentId(), 0)
+ << "Must be called on a worker thread";
+ ContextVector* contexts = contexts_tls_.Get();
+ if (!contexts) {
+ // Thread has already been torn down, and |v8_context| removed. I'm not
+ // sure this can actually happen (depends on in what order blink fires
+ // events), but SW lifetime has bitten us before, so be cautious.
+ return;
+ }
+ auto context_it = FindContext(contexts, v8_context);
+ CHECK(context_it != contexts->end()) << "Worker for " << url
+ << " is not in this set";
+ ScriptContext* context = *context_it;
+ DCHECK_EQ(url, context->url());
+ context->Invalidate();
+ contexts->erase(context_it);
+}
+
+void WorkerScriptContextSet::WillStopCurrentWorkerThread() {
+ content::WorkerThread::RemoveObserver(this);
+ ContextVector* contexts = contexts_tls_.Get();
+ DCHECK(contexts);
+ for (ScriptContext* context : *contexts)
+ context->Invalidate();
+ contexts_tls_.Set(nullptr);
+ delete contexts;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/worker_script_context_set.h b/chromium/extensions/renderer/worker_script_context_set.h
new file mode 100644
index 00000000000..dd33a77d6a7
--- /dev/null
+++ b/chromium/extensions/renderer/worker_script_context_set.h
@@ -0,0 +1,46 @@
+// Copyright 2015 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.
+
+#ifndef EXTENSIONS_RENDERER_WORKER_SCRIPT_CONTEXT_SET_H_
+#define EXTENSIONS_RENDERER_WORKER_SCRIPT_CONTEXT_SET_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/threading/thread_local.h"
+#include "content/public/child/worker_thread.h"
+#include "url/gurl.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+class ScriptContext;
+
+// A set of ScriptContexts owned by worker threads. Thread safe.
+class WorkerScriptContextSet : public content::WorkerThread::Observer {
+ public:
+ WorkerScriptContextSet();
+
+ ~WorkerScriptContextSet() override;
+
+ // Inserts |context| into the set. Contexts are stored in TLS.
+ void Insert(scoped_ptr<ScriptContext> context);
+
+ // Removes the ScriptContext associated with |v8_context| which was added for
+ // |url| (used for sanity checking).
+ void Remove(v8::Local<v8::Context> v8_context, const GURL& url);
+
+ private:
+ // WorkerThread::Observer:
+ void WillStopCurrentWorkerThread() override;
+
+ // Implement thread safety by storing each ScriptContext in TLS.
+ base::ThreadLocalPointer<ScopedVector<ScriptContext>> contexts_tls_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkerScriptContextSet);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_WORKER_SCRIPT_CONTEXT_SET_H_